This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

CC1352P7: Is there maximum CoAP payload size in Wi-SUN?

Part Number: CC1352P7
Other Parts Discussed in Thread: WI-SUN

Tool/software:

Hi team,

I have a project running Wi-SUN with a CoAP node that receives data via UART and forwards it to border router. The data is received by UART with fallback functionality.

It seems to be working fine, but when the payload exceeds certain size, the CC seems to be falling into a for(;;) loop. More specifically, with my tests I can successfully send payloads with size of 314 bytes, but when I try sending 328 bytes it stops working.

At first I thought it could be something related to UART callback maximum buffer size, but I defined rxBuffer[1000] and I started a debug session and I could check that UART is working fine and the size of the message is not causing any problem to this peripheral. While debugging, I can see that the code successfully enters the expected case (also another reason to believe it is not a peripheral issue) and reaches the coap_service_request_send() function.

After reaching the request_send function, I did not check step by step but it ends up falling in SysCallback_defaultAbort() eternal loop:

So, is there a maximum payload that CoAP messaging supports with Wi-SUN? Even if it has a maximum size, it seems like 350 bytes is a quite low value.

How can I manage to send longer messages?

Thank you in advance,
Eduardo.

  • Hi Eduardo,

    I would expect this to work no matter the packet size. 

    Can you post some more debug information? E.g. pause the debug session and post the call stack. How is the stack and heap doing when this happens?

    Cheers,

    Marie H

  • Hi Eduardo,

    there is definitely an error in you application code. I tested it with a payload of 1024 bytes and everything works fine.

    You can look at my application code (ns_coap_node - application.c:

    /*
     * Copyright (c) 2015-2019, Texas Instruments Incorporated
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions
     * are met:
     *
     * *  Redistributions of source code must retain the above copyright
     *    notice, this list of conditions and the following disclaimer.
     *
     * *  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.
     *
     * *  Neither the name of Texas Instruments Incorporated 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 OWNER 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.
     */
    
    /*
     *  ======== application.c ========
     */
    
    #ifndef WISUN_NCP_ENABLE
    #undef EXCLUDE_TRACE
    #endif
    #include "mbed_config_app.h"
    #include <unistd.h>
    #include <stdint.h>
    #include <stddef.h>
    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <assert.h>
    
    /* Driver Header files */
    #include <ti/drivers/GPIO.h>
    #include <ti/drivers/SPI.h>
    #include <ti/drivers/dpl/ClockP.h>
    
    /* Driver configuration */
    #include "ti_drivers_config.h"
    #include "ti_wisunfan_config.h"
    
    #include "nsconfig.h"
    #include "mesh_system.h"
    #include "socket_api.h"
    #include "ip6string.h"
    #include "net_interface.h"
    #include "wisun_tasklet.h"
    #include "ns_trace.h"
    #include "fhss_config.h"
    #include "randLIB.h"
    #include "ws_management_api.h"
    
    #include "6LoWPAN/ws/ws_common_defines.h"
    #include "Common_Protocols/ipv6_constants.h"
    #include "6LoWPAN/ws/ws_common.h"
    #include "6LoWPAN/ws/ws_config.h"
    #include "udp.h"
    
    #include "Core/include/ns_address_internal.h"
    #include "NWK_INTERFACE/Include/protocol_abstract.h"
    #include "NWK_INTERFACE/Include/protocol.h"
    #include "6LoWPAN/ws/ws_bootstrap.h"
    
    #include "nsdynmemLIB.h"
    
    #include "net_rpl.h"
    #include "RPL/rpl_protocol.h"
    #include "RPL/rpl_policy.h"
    #include "RPL/rpl_control.h"
    #include "RPL/rpl_objective.h"
    #include "RPL/rpl_upward.h"
    #include "RPL/rpl_downward.h"
    #include "RPL/rpl_structures.h"
    
    #ifdef WISUN_NCP_ENABLE
    /* OpenThread Internal/Example Header files */
    #include "otsupport/otrtosapi.h"
    #include "openthread/ncp.h"
    #include "platform/system.h"
    #elif defined(COAP_SERVICE_ENABLE)
    #include "coap_service_api.h"
    #include "eventOS_event_timer.h"
    #endif
    
    #include "application.h"
    #include "ti_wisunfan_features.h"
    
    #ifdef COAP_OAD_ENABLE
    #include "oad.h"
    #endif
    
    #include "fh_map_direct.h"
    #include "fh_pib.h"
    #include "fh_nt.h"
    
    #include <ti/drivers/UART2.h> //change
    
    
    /******************************************************************************
    Defines & enums
     *****************************************************************************/
    #define TRACE_GROUP "main"
    
    #define SEND_BUF_SIZE 20
    #define multicast_addr_str "ff15::810a:64d1"
    //#define multicast_addr_str "fd00:7283:7e00:0:212:4b00:1ca1:72ff"
    #define UDP_PORT 1234
    #define UDP_PORT_ECHO              7        /* Echo Protocol - RFC 862 */
    #define MASTER_GROUP 0
    #define MY_GROUP 1
    #define NOT_INITIALIZED -1
    
    #ifdef COAP_SERVICE_ENABLE
    #define COAP_JOIN_URI "join"
    #define COAP_LED_URI "led"
    #define COAP_RSSI_URI "rssi"
    #define COAP_TEST_METRICS_URI "metrics"
    #ifdef COAP_PANID_LIST
    #define COAP_PANID_LIST_ALLOW_URI "panid/allow"
    #define COAP_PANID_LIST_DENY_URI "panid/deny"
    #define COAP_PANID_LIST_BULK_URI "panid/bulk"
    #define COAP_PANID_REDISCOVER_URI "panid/rediscover"
    
    #define PANID_LIST_ACTION_ADD 0
    #define PANID_LIST_ACTION_DEL 1
    
    #endif //COAP_PANID_LIST
    
    #define COAP_RLED_ID 0
    #define COAP_GLED_ID 1
    
    #ifdef COAP_PANID_LIST
    #define PAN_REDISCOVER_BLINK_TIMER_ID 0
    #define PAN_REDISCOVER_JOIN_RESP_TIMER_ID 1
    
    #define PAN_REDISCOVER_JOIN_TIMEOUT_SEC     1800 // 30 min
    #define PAN_REDISCOVER_JOIN_REQ_TIMEOUT_SEC 2    // 2 sec
    
    typedef enum pan_rediscover_evt {
        PAN_REDISCOVER_INIT_EVT    = 0,
        PAN_REDISCOVER_START_EVT   = 1,
        PAN_REDISCOVER_WAIT_EVT    = 2,
        PAN_REDISCOVER_JOIN_EVT    = 3,
        PAN_REDISCOVER_RESTART_EVT = 4,
    } pan_rediscover_evt_t;
    #endif //COAP_PANID_LIST
    
    #endif // COAP_SERVICE_ENABLE
    
    typedef enum connection_status {
        CON_STATUS_LOCAL_UP           = 0,        /*!< local IP address set */
        CON_STATUS_GLOBAL_UP          = 1,        /*!< global IP address set */
        CON_STATUS_DISCONNECTED       = 2,        /*!< no connection to network */
        CON_STATUS_CONNECTING         = 3,        /*!< connecting to network */
        CON_STATUS_ERROR_UNSUPPORTED  = 4
    } connection_status_t;
    
    #define COAP_UPDATE_NUM_CARS_URI "upd_num_cars"
    
    /******************************************************************************
     Static & Global Variables
     *****************************************************************************/
    static connection_status_t _connect_status;
    /*static*/ volatile bool connectedFlg = false;
    /*static*/ int8_t interface_id = NOT_INITIALIZED;
    static bool _blocking = false;
    static bool _configured = false;
    
    static int8_t socket_id;
    static uint8_t send_buf[SEND_BUF_SIZE] = {0};
    static uint8_t send_buf_unicast[SEND_BUF_SIZE] = {0};
    
    static uint8_t recv_buffer[SEND_BUF_SIZE] = {0};
    static uint8_t multi_cast_addr[16] = {0};
    static uint8_t uni_cast_addr[16] = {0};
    static ns_address_t send_addr = {0};
    static ns_address_t send_addr_unicast = {0};
    extern bool sent_dao;
    extern uint8_t root_unicast_addr[16];
    static bool bcast_send = false;
    static bool unicast_send = true;
    
    #ifdef NWK_TEST
    uint32_t ticks_before_joining = 0;
    uint32_t ticks_after_joining = 0;
    #endif //NWK_TEST
    
    #ifdef COAP_SERVICE_ENABLE
    int8_t service_id = -1;
    static uint8_t led_state[2];
    
    #ifdef COAP_PANID_LIST
    static int8_t pan_rediscover_tasklet_id = -1;
    extern uint16_t panid_list_clear_timeout_sec;
    #endif
    
    #endif
    
    #ifdef WISUN_NCP_ENABLE
    int8_t ncp_tasklet_id = -1;
    otInstance *OtStack_instance = NULL;
    
    otError nanostack_net_if_up(void);
    otError nanostack_net_stack_up(void);
    #ifdef WISUN_AUTO_START
    static inline void auto_start_assert_led();
    static inline void autoStartSignal();
    #endif //WISUN_AUTO_START
    #endif //WISUN_NCP_ENABLE
    
    #ifdef WISUN_TEST_METRICS
    static uint32_t num_pkts = 0;
    static void handle_message(char* msg);
    
    #ifdef WISUN_TEST_MPL_UDP
    static void get_txFrameInfo(char* msg, uint16_t* txIdx, uint32_t* txbfio);
    extern void timac_GetBC_Slot_BFIO(uint16_t *slot, uint32_t *bfio);
    #endif
    
    #endif
    
    /* variables to help fetch rssi of neighbor nodes */
    uint8_t cur_num_nbrs;
    uint8_t nbr_idx = 0;
    nbr_node_metrics_t nbr_nodes_metrics[SIZE_OF_NEIGH_LIST];
    
    uint8_t get_current_net_state(void);
    
    ti_wisun_config_t ti_wisun_config =
    {
        .rapid_join = FEATURE_RAPID_JOIN_ENABLE,
        .mpl_low_latency = FEATURE_MPL_LOW_LATENCY_ENABLE,
        .rapid_disconnect_detect_br = FEATURE_RAPID_DISCONNECT_DETECT_BR_SEC,
        .rapid_disconnect_detect_rn = FEATURE_RAPID_DISCONNECT_DETECT_RN_SEC,
        .auth_type  = NETWORK_AUTH_TYPE,
        .use_fixed_gtk_keys = false,
        .force_star_topology = FEATURE_FORCE_STAR_TOPOLOGY,
        .fixed_gtk_keys = {
            FIXED_GTK_KEY_1,
            FIXED_GTK_KEY_2,
            FIXED_GTK_KEY_3,
            FIXED_GTK_KEY_4,
        },
    };
    
    configurable_props_t cfg_props = { .phyTxPower = CONFIG_TRANSMIT_POWER, \
                                       .ccaDefaultdBm = CONFIG_CCA_THRESHOLD, \
                                       .uc_channel_function = CONFIG_CHANNEL_FUNCTION, \
                                       .uc_fixed_channel = CONFIG_UNICAST_FIXED_CHANNEL_NUM, \
                                       .uc_dwell_interval = CONFIG_UNICAST_DWELL_TIME,\
                                       .bc_channel_function = 0, \
                                       .bc_fixed_channel = 0, \
                                       .bc_interval = 0,\
                                       .bc_dwell_interval = 0, \
                                       .pan_id = CONFIG_PAN_ID, \
                                       .network_name = CONFIG_NETNAME, \
                                       .bc_channel_list = CONFIG_BROADCAST_CHANNEL_MASK, \
                                       .uc_channel_list = CONFIG_UNICAST_CHANNEL_MASK, \
                                       .async_channel_list = CONFIG_ASYNC_CHANNEL_MASK, \
                                       .wisun_device_type = CONFIG_WISUN_DEVICE_TYPE, \
                                       .ch0_center_frequency = CONFIG_CENTER_FREQ * 1000, \
                                       .config_channel_spacing = CONFIG_CHANNEL_SPACING, \
                                       .config_phy_id = CONFIG_PHY_ID, \
                                       .config_reg_domain = CONFIG_REG_DOMAIN, \
                                       .operating_class = CONFIG_OP_MODE_CLASS, \
                                       .operating_mode = CONFIG_OP_MODE_ID, \
                                       .hwaddr = CONFIG_INVALID_HWADDR};
    
    
    /******************************************************************************
    Function declarations Local & Global
     *****************************************************************************/
    #ifdef FEATURE_TIMAC_SUPPORT
    extern void timacExtaddressRegister();
    #endif
    
    mesh_error_t nanostack_wisunInterface_bringup();
    mesh_error_t nanostack_wisunInterface_connect(bool blocking);
    void nanostack_wait_till_connect();
    
    #ifdef WISUN_NCP_ENABLE
    extern void platformNcpSendProcess();
    extern void platformNcpSendAsyncProcess();
    #endif
    
    #ifdef COAP_SERVICE_ENABLE
    static void pan_rediscover_tasklet_start(void);
    static void pan_rediscover_update(uint8_t event_type);
    static void pan_rediscover_tasklet(arm_event_s *event);
    static int coap_recv_cb(int8_t service_id, uint8_t source_address[static 16],
                     uint16_t source_port, sn_coap_hdr_s *request_ptr);
    static int coap_recv_cb_rssi(int8_t service_id, uint8_t source_address[static 16],
                     uint16_t source_port, sn_coap_hdr_s *request_ptr);
    static int coap_panid_list_cb(int8_t service_id, uint8_t source_address[static 16],
                     uint16_t source_port, sn_coap_hdr_s *request_ptr);
    
    #ifdef WISUN_TEST_METRICS
    static int coap_recv_cb_tstmetrics(int8_t service_id, uint8_t source_address[static 16],
                     uint16_t source_port, sn_coap_hdr_s *request_ptr);
    #endif
    #endif
    
    #ifdef WISUN_TEST_METRICS
    void get_test_metrics(test_metrics_s *test_metrics);
    #endif
    
    /******************************************************************************
    Function definitions
     *****************************************************************************/
    
    /*!
     * Callback for handling any status while a node is joining a network.
     */
    void nanostackNetworkHandler(mesh_connection_status_t status)
    {
        tr_debug("nanostackNetworkHandler: %x", status);
    
        if (_blocking) {
            if (_connect_status == CON_STATUS_CONNECTING
                    && (status == MESH_CONNECTED || status == MESH_CONNECTED_LOCAL
                        || status == MESH_CONNECTED_GLOBAL)) {
                connectedFlg = true;
            } else if (status == MESH_DISCONNECTED) {
                connectedFlg = false;
            }
        }
    
        if (status == MESH_CONNECTED) {
            uint8_t temp_ipv6_global[16];
            uint8_t temp_ipv6_local[16];
            if (arm_net_address_get(interface_id, ADDR_IPV6_LL, temp_ipv6_local) == 0) {
                tr_info("nanostackNetworkHandler: CON_STATUS_LOCAL_UP, IP %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
                    temp_ipv6_local[0], temp_ipv6_local[1],
                    temp_ipv6_local[2], temp_ipv6_local[3],
                    temp_ipv6_local[4], temp_ipv6_local[5],
                    temp_ipv6_local[6], temp_ipv6_local[7],
                    temp_ipv6_local[8], temp_ipv6_local[9],
                    temp_ipv6_local[10], temp_ipv6_local[11],
                    temp_ipv6_local[12], temp_ipv6_local[13],
                    temp_ipv6_local[14], temp_ipv6_local[15]);
                _connect_status = CON_STATUS_LOCAL_UP;
            }
            if (arm_net_address_get(interface_id, ADDR_IPV6_GP, temp_ipv6_global) == 0
                    && (memcmp(temp_ipv6_global, temp_ipv6_local, 16) != 0)) {
                tr_info("nanostackNetworkHandler: CON_STATUS_GLOBAL_UP, IP %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
                    temp_ipv6_global[0], temp_ipv6_global[1],
                    temp_ipv6_global[2], temp_ipv6_global[3],
                    temp_ipv6_global[4], temp_ipv6_global[5],
                    temp_ipv6_global[6], temp_ipv6_global[7],
                    temp_ipv6_global[8], temp_ipv6_global[9],
                    temp_ipv6_global[10], temp_ipv6_global[11],
                    temp_ipv6_global[12], temp_ipv6_global[13],
                    temp_ipv6_global[14], temp_ipv6_global[15]);
                _connect_status = CON_STATUS_GLOBAL_UP;
            }
        } else if (status == MESH_CONNECTED_LOCAL) {
            tr_info("nanostackNetworkHandler: CON_STATUS_LOCAL_UP");
            _connect_status = CON_STATUS_LOCAL_UP;
        } else if (status == MESH_CONNECTED_GLOBAL) {
            tr_info("nanostackNetworkHandler: CON_STATUS_GLOBAL_UP");
            _connect_status = CON_STATUS_GLOBAL_UP;
        } else if (status == MESH_BOOTSTRAP_STARTED || status == MESH_BOOTSTRAP_FAILED) {
            tr_info("nanostackNetworkHandler: CON_STATUS_CONNECTING");
            _connect_status = CON_STATUS_CONNECTING;
        } else {
            _connect_status = CON_STATUS_DISCONNECTED;
            tr_info("nanostackNetworkHandler: CON_STATUS_DISCONNECTED");
        }
    }
    
    /*!
     * Callback for handling any activity on the udp socket
     */
    void socket_callback(void *cb)
    {
        socket_callback_t *sock_cb = (socket_callback_t *) cb;
    
    #ifdef WISUN_TEST_METRICS
        int16_t len;
        ns_address_t source_addr;
    #endif
        tr_debug("socket_callback() sock=%d, event=0x%x, interface=%d, data len=%d",
                 sock_cb->socket_id, sock_cb->event_type, sock_cb->interface_id, sock_cb->d_len);
    
        switch (sock_cb->event_type & SOCKET_EVENT_MASK) {
            case SOCKET_DATA:
                tr_info("socket_callback: SOCKET_DATA, sock=%d, bytes=%d", sock_cb->socket_id, sock_cb->d_len);
    
    #ifdef WISUN_TEST_METRICS
    //            tr_mpl("socket_callback: SOCKET_DATA, sock=%d, bytes=%d", sock_cb->socket_id, sock_cb->d_len);
    
                /* Convert string addr to ipaddr array */
                len = socket_recvfrom(socket_id, recv_buffer, sizeof(recv_buffer), 0, &source_addr);
                if(len > 0)
                  {
                      num_pkts++;
    #ifdef WISUN_TEST_MPL_UDP
                      uint16_t slotIdx_tx, slotIdx_rx;
                      uint32_t bfio_tx, bfio_rx, latency, macBcInterval;
                      get_txFrameInfo ((char*)recv_buffer, &slotIdx_tx, &bfio_tx);
                      timac_GetBC_Slot_BFIO(&slotIdx_rx, &bfio_rx);
                      MAP_FHPIB_get(FHPIB_BC_INTERVAL, &macBcInterval);
                      latency = (bfio_rx - bfio_tx) + (slotIdx_rx - slotIdx_tx) * macBcInterval;
                      tr_mpl("Latency: %d L:%d B1:%d S1:%d B2:%d S2:%d BI:%d\r\n", latency, len, bfio_tx, slotIdx_tx, bfio_rx, slotIdx_rx, macBcInterval);
    #else
                      tr_mpl("Recv[%d]: %s, Pkts:%d", len, recv_buffer, num_pkts);
    #endif
                  }
                  else if(NS_EWOULDBLOCK != len)
                  {
                      tr_mpl("Recv error %x", len);
                  }
    #endif
                break;
            case SOCKET_CONNECT_DONE:
                tr_info("socket_callback: SOCKET_CONNECT_DONE");
                break;
            case SOCKET_CONNECT_FAIL:
                tr_info("socket_callback: SOCKET_CONNECT_FAIL");
                break;
            case SOCKET_CONNECT_AUTH_FAIL:
                tr_info("socket_callback: SOCKET_CONNECT_AUTH_FAIL");
                break;
            case SOCKET_INCOMING_CONNECTION:
                tr_info("socket_callback: SOCKET_INCOMING_CONNECTION");
                break;
            case SOCKET_TX_FAIL:
                tr_info("socket_callback: SOCKET_TX_FAIL");
                break;
            case SOCKET_CONNECT_CLOSED:
                tr_info("socket_callback: SOCKET_CONNECT_CLOSED");
                break;
            case SOCKET_CONNECTION_RESET:
                tr_info("socket_callback: SOCKET_CONNECTION_RESET");
                break;
            case SOCKET_NO_ROUTE:
                tr_info("socket_callback: SOCKET_NO_ROUTE");
                break;
            case SOCKET_TX_DONE:
                tr_info("socket_callback: SOCKET_TX_DONE");
                break;
            case SOCKET_NO_RAM:
                tr_info("socket_callback: SOCKET_NO_RAM");
                break;
            case SOCKET_CONNECTION_PROBLEM:
                tr_info("socket_callback: SOCKET_CONNECTION_PROBLEM");
                break;
            default:
                break;
        }
    }
    
    /*!
     * Setup udp socket and bind to a specific port number
     */
    bool udpSocketSetup(void)
    {
        int8_t ret;
        ns_ipv6_mreq_t mreq;
        ns_address_t bind_addr;
    
        tr_info("opening udp socket");
        socket_id = socket_open(SOCKET_UDP, 0, socket_callback);
        if (socket_id < 0) {
            tr_debug("socket open failed with error %d", socket_id);
            return false;
        }
    
        // how many hops the multicast message can go
        static const int16_t multicast_hops = 10;
        socket_setsockopt(socket_id, SOCKET_IPPROTO_IPV6, SOCKET_IPV6_MULTICAST_HOPS, &multicast_hops, sizeof(multicast_hops));
    
        static const int32_t buf_size = 20;
        int32_t rtn = 20;
    //    rtn = socket_setsockopt(socket_id, SOCKET_SOL_SOCKET, SOCKET_SO_RCVBUF, &buf_size, sizeof buf_size);
        tr_info("set rx buffer len %x, status %x", buf_size, rtn);
    /*
        rtn = socket_setsockopt(socket_id, SOCKET_SOL_SOCKET, SOCKET_SO_SNDBUF, &buf_size, sizeof buf_size);
        tr_info("set Tx buffer len %x, status %x", buf_size, rtn);
    */
    
        bind_addr.type = ADDRESS_IPV6;
        memcpy(bind_addr.address, ns_in6addr_any, 16);
        bind_addr.identifier = UDP_PORT_TEST;
        ret = socket_bind(socket_id, &bind_addr);
        if (ret < 0) {
            tr_error("socket bind failed with error %d", ret);
            return false;
        }
        return true;
    }
    
    /*!
     * Configure the network settings like network name,
     * regulatory domain etc. before starting the network
     */
    extern const char *ti154stack_lib_version;
    extern const char *ti154stack_lib_date;
    extern const char *ti154stack_lib_time;
    mesh_error_t nanostack_wisunInterface_configure(void)
    {
        int ret;
    
        tr_info("Library info | Date: %s, Time: %s, Version: %s", ti154stack_lib_date, ti154stack_lib_time,
                ti154stack_lib_version);
    
        if (_configured) {
            // Already configured
            return MESH_ERROR_NONE;
        }
    #ifndef TI_WISUN_FAN_OPT
        ret = ws_management_network_name_set(interface_id, cfg_props.network_name);
        if (ret < 0) {
            return MESH_ERROR_PARAM;
        }
    
    
        ret = ws_management_regulatory_domain_set(interface_id, CONFIG_REG_DOMAIN,
                                                                CONFIG_OP_MODE_CLASS,
                                                                CONFIG_OP_MODE_ID);
        if (ret < 0) {
            return MESH_ERROR_PARAM;
        }
    
    
        ret = ws_management_fhss_unicast_channel_function_configure(interface_id, cfg_props.uc_channel_function,
                                                                                  cfg_props.uc_fixed_channel,
                                                                                  cfg_props.uc_dwell_interval);
        if (ret < 0) {
            return MESH_ERROR_PARAM;
        }
    
    
        ret = ws_management_fhss_broadcast_channel_function_configure(interface_id, cfg_props.bc_channel_function,
                                                                                    cfg_props.bc_fixed_channel,
                                                                                    cfg_props.bc_dwell_interval,
                                                                                    cfg_props.bc_interval);
        if (ret < 0) {
            return MESH_ERROR_PARAM;
        }
    
    
        ret = ws_management_network_size_set(interface_id, MBED_CONF_MBED_MESH_APP_WISUN_NETWORK_SIZE);
        if (ret < 0) {
            return MESH_ERROR_PARAM;
        }
    #else
        ws_cfg_set_intferface_all();
    #endif //TI_WISUN_FAN_OPT
    
        _configured = true;
        return MESH_ERROR_NONE;
    }
    
    /*!
     *
     */
    mesh_error_t nanostack_wisunInterface_bringup()
    {
        int8_t device_id = 0;
    
    #ifndef FEATURE_TIMAC_SUPPORT
        NanostackTiRfPhy_init();
        device_id = NanostackTiRfPhy_rf_register();
    #else
        timacExtaddressRegister();
    #endif
        // After the RF is up, we can seed the random from it.
        randLIB_seed_random();
    
        wisun_tasklet_init();
    
        interface_id =  wisun_tasklet_network_init(device_id);
        if (interface_id < 0) {
            return MESH_ERROR_UNKNOWN;
        }
    
        return MESH_ERROR_NONE;
    }
    
    /*!
     * Connect to the Wi-SUN network. Should be called only after calling
     * nanostack_wisunInterface_bringup(). This function also submits
     * the network handler function for handling different events
     * during the connection process, while calling "connect".
     * Only blocking mode has been tested. So, it is recommended for
     * the input parameter blocking to be set to true.
     */
    mesh_error_t nanostack_wisunInterface_connect(bool blocking)
    {
    
        int8_t tasklet_id;
    
        _blocking = blocking;
    
        tasklet_id = wisun_tasklet_connect(nanostackNetworkHandler, interface_id);
    
        if (tasklet_id < 0) {
            return MESH_ERROR_UNKNOWN;
        }
    
       return MESH_ERROR_NONE;
    }
    
    /*!
     * Connect to the Wi-SUN network. Should be called only after calling
     * nanostack_wisunInterface_bringup().
     * It is recommended to start the node join process in blocking mode
     */
    void nanostack_wait_till_connect()
    {
        uint8_t _net_state;
        if (_blocking)
        {
    #ifdef NWK_TEST
            ticks_before_joining = ClockP_getSystemTicks();
    #endif //NWK_TEST
    
            // wait till connection goes through
            while(connectedFlg == false)
                {
                    /* Toggle red LED at rate of state*100 ms. Slower the blinking, closer it is to joining */
                    _net_state = get_current_net_state();
                    usleep((_net_state + 1) * 100000);
                    // max usleep value possible is 1000000
                    GPIO_toggle(CONFIG_GPIO_RLED);
                }
            /* Solid Green to Indicate that node has Joined */
            GPIO_write(CONFIG_GPIO_RLED, CONFIG_GPIO_LED_OFF);
            GPIO_write(CONFIG_GPIO_GLED, CONFIG_GPIO_LED_ON);
    
    #ifdef NWK_TEST
            ticks_after_joining = ClockP_getSystemTicks();
    #endif //NWK_TEST
        }
    }
    
    #if !defined(WISUN_NCP_ENABLE) && !defined(NWK_TEST)
    /*!
     * Interrupt handler for handling button presses on the
     * Launch pad.
     * Press button 1 to start sending of a broadcast packets.
     * To disable sending of broadcast packets, press button 2
     * again.
     * Press button 2 to trigger sending of a unicast packet.
     * To trigger sending of the next unicast packet, press
     * button 2 again.
     * These button presses should be exercised only after the
     * node joins the network.
     */
    static int coap_client_response_acknowledge(int8_t service_id, uint8_t source_address[static 16],
            uint16_t source_port, sn_coap_hdr_s *request_ptr)
    {
        if (request_ptr->msg_code == COAP_MSG_CODE_RESPONSE_CONTENT && sizeof(request_ptr->payload_len) != 0)
        {
            UART2_Handle uart_handle;
            UART2_Params uartParams;
            size_t bytesRead;
            size_t bytesWritten = 0;
    
            UART2_Params_init(&uartParams);
            uartParams.baudRate = 115200;
            uartParams.readMode = UART2_Mode_NONBLOCKING;
            uartParams.writeMode = UART2_Mode_NONBLOCKING;
    
            uart_handle = UART2_open(UART_BUTTON, &uartParams);
            if (uart_handle == NULL) {
                // UART2_open() failed
                while (1);
            }
            UART2_write(uart_handle, request_ptr->payload_ptr, request_ptr->payload_len, &bytesWritten);
            for (int i = 0; i<4; i++)
            {
                sleep(1);
                GPIO_toggle(CONFIG_GPIO_RLED);
            }
            UART2_close(uart_handle);
        }
        return 0;
    }
    static void coap_handle_button_press()
    {
        if(connectedFlg)
        {
            uint8_t charArray[1024];
            for (size_t i = 0; i < sizeof(charArray); i++)
            {
                charArray[i] = 0xF;
            }
            coap_service_request_send(service_id, 0, root_unicast_addr, COAP_PORT,
                                COAP_MSG_TYPE_CONFIRMABLE, COAP_MSG_CODE_REQUEST_POST, COAP_UPDATE_NUM_CARS_URI,
                                COAP_CT_TEXT_PLAIN, (const uint8_t *)charArray, sizeof(charArray), coap_client_response_acknowledge);
        }
    }
    static void btn_interrupt_handler(uint8_t index)
    {
    //    if(index == CONFIG_GPIO_BTN1)
    //    {
    //        if(bcast_send == true)
    //        {
    //            bcast_send = false;
    //        }
    //        else
    //        {
    //            bcast_send = true;
    //        }
    //    }
    //    else if(index == CONFIG_GPIO_BTN2)
    //    {
    //        if(unicast_send == true)
    //        {
    //            unicast_send = false;
    //        }
    //        else
    //        {
    //            unicast_send = true;
    //        }
    //    }
        coap_handle_button_press();
    }
    #endif //WISUN_NCP_ENABLE, NWK_TEST
    
    /*!
     * Process received message
     */
    static void handle_message(char* msg) {
        uint8_t state=0;
        uint16_t group=0xffff;
    
        if (strstr(msg, "t:lights;") == NULL) {
           return;
        }
    
        if (strstr(msg, "s:1;") != NULL) {
            state = 1;
        }
        else if (strstr(msg, "s:0;") != NULL) {
            state = 0;
        }
    
        // 0==master, 1==default group
        char *msg_ptr = strstr(msg, "g:");
        if (msg_ptr) {
            char *ptr;
            group = strtol(msg_ptr, &ptr, 10);
        }
    
        // in this example we only use one group
        if (group==MASTER_GROUP || group==MY_GROUP) {
            GPIO_write(CONFIG_GPIO_RLED, state);
        }
    }
    
    #ifdef WISUN_TEST_MPL_UDP
    /*!
     * Retrieve Idx and BFIO from received message
     */
    void get_txFrameInfo(char* msg, uint16_t* txIdx, uint32_t* txbfio) {
        char *txinfo;
    
        // Format from Border Router for the payload is as follows
        // snprintf(send_buf, sizeof(send_buf), "Id:%d:bfio:%u", slotIdx,bfio);
        txinfo = strstr(msg, "Id:");
        if (txinfo == NULL)
        {
            // Idx and BFIO for tx is not in the message payload
            *txIdx = 0;
            *txbfio = 0;
        }
        else
        {
            char *ptr;
            // Retrieve slot ID index
            *txIdx = strtol(txinfo+3, &ptr, 10);
    
            // Retrieve BFIO
            txinfo = strstr(ptr, "bfio:");
            *txbfio = strtol(txinfo+5, &ptr, 10);
        }
        return;
    }
    #endif
    
    #ifdef COAP_SERVICE_ENABLE
    #ifdef COAP_PANID_LIST
    static void pan_rediscover_tasklet_start(void)
    {
        pan_rediscover_tasklet_id = eventOS_event_handler_create(
            &pan_rediscover_tasklet,
            PAN_REDISCOVER_INIT_EVT);
    }
    
    static void pan_rediscover_update(uint8_t event_type)
    {
        arm_event_s event = {
               .sender = 0,
               .receiver = pan_rediscover_tasklet_id,
               .priority = ARM_LIB_LOW_PRIORITY_EVENT,
               .event_type = event_type,
               .event_id = 0,
               .event_data = 0
        };
        eventOS_event_send(&event);
    }
    
    extern void ccfg_read_mac_addr(uint8_t *mac_addr);
    static void pan_rediscover_tasklet(arm_event_s *event)
    {
        pan_rediscover_evt_t event_type;
        protocol_interface_info_entry_t *cur;
        uint8_t net_state = 0;
        uint16_t blink_rate;
        int ret;
        uint8_t hwAddr[8];
    
        event_type = (pan_rediscover_evt_t) event->event_type;
        cur = protocol_stack_interface_info_get(IF_6LoWPAN);
    
        switch (event_type) {
            // Init event called after tasklet creation
            case PAN_REDISCOVER_INIT_EVT:
                break;
            case PAN_REDISCOVER_START_EVT:
                GPIO_write(CONFIG_GPIO_RLED, CONFIG_GPIO_LED_OFF);
                GPIO_write(CONFIG_GPIO_GLED, CONFIG_GPIO_LED_OFF);
                pan_rediscover_update(PAN_REDISCOVER_WAIT_EVT);
                break;
            case PAN_REDISCOVER_WAIT_EVT:
                net_state = get_current_net_state();
                if(net_state != 5)
                {
                    // Toggle red LED at rate of state*100 ms. Slower the blinking, closer it is to joining
                    blink_rate = (net_state + 1) * 100;
                    GPIO_toggle(CONFIG_GPIO_RLED);
    
                    // Timer to recheck net_state (toggles LED just as in initial join)
                    eventOS_event_timer_request(PAN_REDISCOVER_BLINK_TIMER_ID, PAN_REDISCOVER_WAIT_EVT,
                                                pan_rediscover_tasklet_id, blink_rate);
                }
                else
                {
                    // Solid Green to Indicate that node has joined
                    GPIO_write(CONFIG_GPIO_RLED, CONFIG_GPIO_LED_OFF);
                    GPIO_write(CONFIG_GPIO_GLED, CONFIG_GPIO_LED_ON);
                    // Cancel the timeout timer as we have successfully joined
                    pan_rediscover_update(PAN_REDISCOVER_JOIN_EVT);
                }
                break;
            case PAN_REDISCOVER_JOIN_EVT:
                // Get current HW address and add to payload
                ccfg_read_mac_addr(hwAddr);
                // CoAP message to indicate to BR that new device has joined
                coap_service_request_send(service_id, 0, cur->border_router_setup->border_router_gp_adr,
                                          COAP_PORT, COAP_MSG_TYPE_NON_CONFIRMABLE, COAP_MSG_CODE_REQUEST_POST,
                                          COAP_JOIN_URI, COAP_CT_TEXT_PLAIN, hwAddr, 8, NULL);
                // Timer to initiate panid list clear and restart joining process (in case join is lost)
                // Cancelled if bulk panid list set is sent to COAP_PANID_LIST_BULK_URI
                eventOS_event_timer_request(PAN_REDISCOVER_JOIN_RESP_TIMER_ID, PAN_REDISCOVER_RESTART_EVT,
                                            pan_rediscover_tasklet_id, 1000*PAN_REDISCOVER_JOIN_REQ_TIMEOUT_SEC);
                break;
            case PAN_REDISCOVER_RESTART_EVT:
                // Cancel lingering wait event if it is still in the event queue
                eventOS_event_timer_cancel(PAN_REDISCOVER_BLINK_TIMER_ID, pan_rediscover_tasklet_id);
                api_panid_filter_list_remove(PANID_ALLOW_LIST_E, 0, true);
                api_panid_filter_list_remove(PANID_DENY_LIST_E, 0, true);
                nanostack_net_stack_restart(true);
                pan_rediscover_update(PAN_REDISCOVER_START_EVT);
                break;
            default:
                break;
        } // switch(event_type)
    }
    #endif // COAP_PANID_LIST
    
    
    /*!
     * Callback for processing received coap message
     */
    static int coap_recv_cb(int8_t service_id, uint8_t source_address[static 16],
                     uint16_t source_port, sn_coap_hdr_s *request_ptr)
    {
    
        if (request_ptr->msg_code == COAP_MSG_CODE_REQUEST_GET)
        {
            // Send LED states as [RLED_STATE, GLED_STATE] in CoAP response payload
            led_state[COAP_RLED_ID] = GPIO_read(CONFIG_GPIO_RLED);
            led_state[COAP_GLED_ID] = GPIO_read(CONFIG_GPIO_GLED);
            coap_service_response_send(service_id, 0, request_ptr, COAP_MSG_CODE_RESPONSE_CONTENT,
                                       COAP_CT_TEXT_PLAIN, (uint8_t *) &led_state, sizeof(led_state));
        }
        else if (request_ptr->msg_code == COAP_MSG_CODE_REQUEST_POST ||
                 request_ptr->msg_code == COAP_MSG_CODE_REQUEST_PUT)
        {
            bool success = true;
            if (request_ptr->payload_len != 2 || request_ptr->payload_ptr == NULL)
            {
                // Invalid payload length
                success = false;
            }
            else if ((request_ptr->payload_ptr[0] != 0 && request_ptr->payload_ptr[0] != 1) ||
                     (request_ptr->payload_ptr[1] != 0 && request_ptr->payload_ptr[1] != 1))
            {
                // Invalid payload contents
                success = false;
            }
            else
            {
                // Set LED state from payload: [target LED, LED state]
                if (request_ptr->payload_ptr[0] == COAP_RLED_ID)
                {
                    GPIO_write(CONFIG_GPIO_RLED, request_ptr->payload_ptr[1]);
                }
                else if (request_ptr->payload_ptr[0] == COAP_GLED_ID)
                {
                    GPIO_write(CONFIG_GPIO_GLED, request_ptr->payload_ptr[1]);
                }
                else
                {
                    success = false;
                }
            }
    
            if (success)
            {
                coap_service_response_send(service_id, 0, request_ptr, COAP_MSG_CODE_RESPONSE_CHANGED,
                                           COAP_CT_TEXT_PLAIN, NULL, 0);
            }
            else
            {
                coap_service_response_send(service_id, 0, request_ptr, COAP_MSG_CODE_RESPONSE_BAD_REQUEST,
                                           COAP_CT_TEXT_PLAIN, NULL, 0);
            }
        }
        else
        {
            // Delete resource not supported
            coap_service_response_send(service_id, 0, request_ptr, COAP_MSG_CODE_RESPONSE_METHOD_NOT_ALLOWED,
                                       COAP_CT_TEXT_PLAIN, NULL, 0);
        }
        return 0;
    }
    
    #ifdef WISUN_TEST_METRICS
    /*!
     * Callback for processing received coap message for Test metrics
     */
    static int coap_recv_cb_tstmetrics(int8_t service_id, uint8_t source_address[static 16],
                     uint16_t source_port, sn_coap_hdr_s *request_ptr)
    {
        if (request_ptr->msg_code == COAP_MSG_CODE_REQUEST_GET)
        {
            test_metrics_s test_metrics;
            // Send test metrics data
            get_test_metrics(&test_metrics);
            coap_service_response_send(service_id, 0, request_ptr, COAP_MSG_CODE_RESPONSE_CONTENT,
                                       COAP_CT_TEXT_PLAIN, (uint8_t *) &test_metrics, sizeof(test_metrics_s));
        }
        else
        {
            // Post, Put, and Delete resource are not supported for now
            coap_service_response_send(service_id, 0, request_ptr, COAP_MSG_CODE_RESPONSE_METHOD_NOT_ALLOWED,
                                       COAP_CT_TEXT_PLAIN, NULL, 0);
        }
        return 0;
    }
    #endif
    
    #ifdef COAP_PANID_LIST
    static int coap_panid_list_cb(int8_t service_id, uint8_t source_address[static 16],
                     uint16_t source_port, sn_coap_hdr_s *request_ptr)
    {
        uint16_t i, panid_list_len, payload_index;
        panid_list_type_e list_type;
        sn_coap_msg_code_e resp_code;
        uint16_t *panid_list;
        uint16_t *payload;
        uint16_t panid;
        int ret;
    
        // Determine whether to use allowlist, denylist, do bulk update, or do rediscovery
        if (memcmp(request_ptr->uri_path_ptr, COAP_PANID_LIST_ALLOW_URI, request_ptr->uri_path_len) == 0)
        {
            list_type = PANID_ALLOW_LIST_E;
            panid_list = panid_allow_list;
            panid_list_len = MAX_PANID_ALLOW_LIST_LEN;
        }
        else if (memcmp(request_ptr->uri_path_ptr, COAP_PANID_LIST_DENY_URI, request_ptr->uri_path_len) == 0)
        {
            list_type = PANID_DENY_LIST_E;
            panid_list = panid_deny_list;
            panid_list_len = MAX_PANID_DENY_LIST_LEN;
        }
        else if (memcmp(request_ptr->uri_path_ptr, COAP_PANID_REDISCOVER_URI, request_ptr->uri_path_len) == 0)
        {
            // Start PAN rediscover process
            ret = nanostack_net_stack_restart(false);
            if (ret == PANID_STACK_RESTART_SUCCESS)
            {
                coap_service_response_send(service_id, 0, request_ptr, COAP_MSG_CODE_RESPONSE_CHANGED,
                                           COAP_CT_TEXT_PLAIN, NULL, 0);
                pan_rediscover_update(PAN_REDISCOVER_START_EVT);
    
            }
            else if (ret == PANID_STACK_RESTART_NO_RESTART)
            {
                coap_service_response_send(service_id, 0, request_ptr, COAP_MSG_CODE_RESPONSE_VALID,
                                           COAP_CT_TEXT_PLAIN, NULL, 0);
            }
            else
            {
                coap_service_response_send(service_id, 0, request_ptr, COAP_MSG_CODE_RESPONSE_INTERNAL_SERVER_ERROR,
                                           COAP_CT_TEXT_PLAIN, NULL, 0);
            }
            return 0;
        }
        else if (memcmp(request_ptr->uri_path_ptr, COAP_PANID_LIST_BULK_URI, request_ptr->uri_path_len) == 0)
        {
            uint8_t err = 0;
            uint16_t allowlist_len, denylist_len;
    
            // Cancel timer set by PAN_REDISCOVER_JOIN_EVT
            eventOS_event_timer_cancel(PAN_REDISCOVER_JOIN_RESP_TIMER_ID, pan_rediscover_tasklet_id);
    
            /*
             * Format of CoAP PANID bulk update:
             * <2 byte allow/deny list timeout (seconds) +
             * <2 byte allowlist len> + <2 byte denylist len> +
             * <2 byte allowlist entry>*(allowlist len) + <2 byte denylist entry>*(denylist len)
             */
            // Length checking
            if (request_ptr->payload_len < 6) // Min length considering 0 entries in both lists
            {
                err = 1;
            }
            else
            {
                uint16_t total_len = 6;
                allowlist_len = *((uint16_t *) (request_ptr->payload_ptr + 2));
                denylist_len = *((uint16_t *) (request_ptr->payload_ptr + 4));
                total_len += ((allowlist_len + denylist_len)*2);
                if (total_len != request_ptr->payload_len)
                {
                    err = 1;
                }
            }
            if (err == 1)
            {
                coap_service_response_send(service_id, 0, request_ptr, COAP_MSG_CODE_RESPONSE_BAD_REQUEST,
                                           COAP_CT_TEXT_PLAIN, NULL, 0);
                return 0;
            }
    
            // Populate timeout value (sec) if nonzero
            uint16_t payload_timeout = *(uint16_t *)(request_ptr->payload_ptr);
            if (payload_timeout != 0)
            {
                panid_list_clear_timeout_sec = payload_timeout;
            }
    
            // Populate allowlist/denylist
            uint16_t panid_list_start;
            panid_list_type_e remove_list_type;
            // Start counting payload after timeout/size info, and cast to 16 bit array
            uint16_t *panid_list_payload = (uint16_t *)(request_ptr->payload_ptr + 6);
            for (list_type = PANID_ALLOW_LIST_E; list_type <= PANID_DENY_LIST_E; list_type++)
            {
                if (list_type == PANID_ALLOW_LIST_E)
                {
                    remove_list_type = PANID_DENY_LIST_E;
                    panid_list_start = 0;
                    panid_list_len = allowlist_len;
                }
                else // PANID_DENY_LIST_E
                {
                    remove_list_type = PANID_ALLOW_LIST_E;
                    panid_list_start = allowlist_len;
                    panid_list_len = denylist_len;
                }
    
                for (i = 0; i < panid_list_len; i++)
                {
                    panid = *(panid_list_payload + panid_list_start + i);
                    // Add to list
                    ret = api_panid_filter_list_add(list_type, panid);
                    if (ret != PANID_FLTR_UPDATE_SUCCESS)
                    {
                        coap_service_response_send(service_id, 0, request_ptr,
                                                   COAP_MSG_CODE_RESPONSE_INTERNAL_SERVER_ERROR,
                                                   COAP_CT_TEXT_PLAIN, NULL, 0);
                        return 0;
                    }
                    // Remove from opposite list
                    ret = api_panid_filter_list_remove(remove_list_type, panid, false);
                    if (ret != PANID_FLTR_UPDATE_SUCCESS && ret != PANID_FLTR_UPDATE_NO_MATCH)
                    {
                        coap_service_response_send(service_id, 0, request_ptr,
                                                   COAP_MSG_CODE_RESPONSE_INTERNAL_SERVER_ERROR,
                                                   COAP_CT_TEXT_PLAIN, NULL, 0);
                        return 0;
                    }
                }
            }
    
            // Start PAN redicover process
            ret = nanostack_net_stack_restart(false);
            if (ret == PANID_STACK_RESTART_SUCCESS)
            {
                coap_service_response_send(service_id, 0, request_ptr, COAP_MSG_CODE_RESPONSE_CHANGED,
                                           COAP_CT_TEXT_PLAIN, NULL, 0);
                pan_rediscover_update(PAN_REDISCOVER_START_EVT);
    
            }
            else if (ret == PANID_STACK_RESTART_NO_RESTART)
            {
                coap_service_response_send(service_id, 0, request_ptr, COAP_MSG_CODE_RESPONSE_VALID,
                                           COAP_CT_TEXT_PLAIN, NULL, 0);
            }
            else
            {
                coap_service_response_send(service_id, 0, request_ptr, COAP_MSG_CODE_RESPONSE_INTERNAL_SERVER_ERROR,
                                           COAP_CT_TEXT_PLAIN, NULL, 0);
            }
            return 0;
        }
        else
        {
            coap_service_response_send(service_id, 0, request_ptr, COAP_MSG_CODE_RESPONSE_BAD_REQUEST,
                                       COAP_CT_TEXT_PLAIN, NULL, 0);
            return 0;
        }
    
        // Handle GET request
        if (request_ptr->msg_code == COAP_MSG_CODE_REQUEST_GET)
        {
            payload_index = 1; // Reserve index 0 for payload length
            payload = (uint16_t *) malloc(panid_list_len * sizeof(uint16_t));
            memset((uint8_t *)payload, 0, sizeof(panid_list_len * sizeof(uint16_t)));
    
            // Populate payload with all used PAN IDs
            for(i = 0; i < panid_list_len; i++)
            {
                if (panid_list[i] != PANID_UNUSED)
                {
                    payload[payload_index] = panid_list[i];
                    payload_index++;
                }
            }
            payload[0] = payload_index - 1; // First element of payload is size of panid list
    
            // Send used PAN IDs
            coap_service_response_send(service_id, 0, request_ptr, COAP_MSG_CODE_RESPONSE_CONTENT,
                                       COAP_CT_TEXT_PLAIN, (uint8_t *)payload, sizeof(uint16_t)*(payload_index));
        }
        // Handle POST/PUT request
        else if (request_ptr->msg_code == COAP_MSG_CODE_REQUEST_POST ||
                 request_ptr->msg_code == COAP_MSG_CODE_REQUEST_PUT)
        {
            ret = -1;
            if (request_ptr->payload_len == 3)
            {
                panid = *((uint16_t *) (request_ptr->payload_ptr + 1));
                if (request_ptr->payload_ptr[0] == PANID_LIST_ACTION_ADD)
                {
                    ret = api_panid_filter_list_add(list_type, panid);
                    resp_code = COAP_MSG_CODE_RESPONSE_CREATED;
                }
                else if (request_ptr->payload_ptr[0] == PANID_LIST_ACTION_DEL)
                {
                    ret = api_panid_filter_list_remove(list_type, panid, false);
                    resp_code = COAP_MSG_CODE_RESPONSE_DELETED;
                }
            }
    
            if (ret == PANID_FLTR_UPDATE_SUCCESS)
            {
                coap_service_response_send(service_id, 0, request_ptr, resp_code,
                                           COAP_CT_TEXT_PLAIN, NULL, 0);
            }
            else
            {
                coap_service_response_send(service_id, 0, request_ptr,
                                           COAP_MSG_CODE_RESPONSE_INTERNAL_SERVER_ERROR,
                                           COAP_CT_TEXT_PLAIN, NULL, 0);
            }
        }
        else
        {
            // Delete resource not supported
            coap_service_response_send(service_id, 0, request_ptr, COAP_MSG_CODE_RESPONSE_METHOD_NOT_ALLOWED,
                                       COAP_CT_TEXT_PLAIN, NULL, 0);
        }
        return 0;
    }
    #endif // COAP_PANID_LIST
    
    void fetch_neighbor_details();
    
    static int coap_recv_cb_rssi(int8_t service_id, uint8_t source_address[static 16],
                     uint16_t source_port, sn_coap_hdr_s *request_ptr)
    {
    
        if (request_ptr->msg_code == COAP_MSG_CODE_REQUEST_GET)
        {
            fetch_neighbor_details();
    
            coap_service_response_send(service_id, 0, request_ptr, COAP_MSG_CODE_RESPONSE_CONTENT,
                                       COAP_CT_TEXT_PLAIN, (uint8_t *) nbr_nodes_metrics, sizeof(nbr_nodes_metrics));
        }
        else
        {
            // Delete resource not supported
            coap_service_response_send(service_id, 0, request_ptr, COAP_MSG_CODE_RESPONSE_METHOD_NOT_ALLOWED,
                                       COAP_CT_TEXT_PLAIN, NULL, 0);
        }
        return 0;
    }
    
    #endif // COAP_SERVICE_ENABLE
    
    #ifndef WISUN_NCP_ENABLE
    
    /*
     *  ======== mainThread ========
     */
    void *mainThread(void *arg0)
    {
        int16_t ret;
    
        /* Configure the LED pins */
        GPIO_setConfig(CONFIG_GPIO_GLED, GPIO_CFG_OUT_STD | GPIO_CFG_OUT_LOW);
        GPIO_setConfig(CONFIG_GPIO_RLED, GPIO_CFG_OUT_STD | GPIO_CFG_OUT_LOW);
        /* default user LED's */
        GPIO_write(CONFIG_GPIO_GLED, CONFIG_GPIO_LED_OFF);
        GPIO_write(CONFIG_GPIO_RLED, CONFIG_GPIO_LED_OFF);
    
    #ifndef NWK_TEST
        /* Configure the button pins */
        GPIO_setConfig(CONFIG_GPIO_BTN1, GPIO_CFG_IN_PU | GPIO_CFG_IN_INT_FALLING);
        GPIO_setConfig(CONFIG_GPIO_BTN2, GPIO_CFG_IN_PU | GPIO_CFG_IN_INT_FALLING);
    
        /* Setup callback for btn int */
        GPIO_setCallback(CONFIG_GPIO_BTN1, btn_interrupt_handler);
        GPIO_enableInt(CONFIG_GPIO_BTN1);
        GPIO_setCallback(CONFIG_GPIO_BTN2, btn_interrupt_handler);
        GPIO_enableInt(CONFIG_GPIO_BTN2);
    #endif //NWK_TEST
    
        extern const char *ti154stack_lib_version;
        extern const char *ti154stack_lib_date;
        extern const char *ti154stack_lib_time;
        tr_info("Library info | Date: %s, Time: %s, Version: %s", ti154stack_lib_date, ti154stack_lib_time,
                ti154stack_lib_version);
    
        nanostack_wisunInterface_configure();
    
        // Release mutex before blocking
        nanostack_lock();
    
        if( MESH_ERROR_NONE != nanostack_wisunInterface_bringup())
        {
            // release mutex
            nanostack_unlock();
            // do not proceed further
            while(1);
        }
    
        if(MESH_ERROR_NONE != nanostack_wisunInterface_connect(true))
        {
            // release mutex
            nanostack_unlock();
            // do not proceed further
            while(1);
        }
    
        // if here all good so far: proceed further
        // Release mutex before blocking
        nanostack_unlock();
    
    #ifdef NWK_TEST
    
        /* this should only be initialized once */
        nwkTest_init();
    
        while (1)
        {
            nwkTest_run();
            usleep(100000);
        }
    #elif defined(COAP_SERVICE_ENABLE)
        service_id = coap_service_initialize(interface_id, COAP_PORT, 0, NULL, NULL);
        coap_service_register_uri(service_id, COAP_LED_URI,
                                  COAP_SERVICE_ACCESS_GET_ALLOWED |
                                  COAP_SERVICE_ACCESS_PUT_ALLOWED |
                                  COAP_SERVICE_ACCESS_POST_ALLOWED,
                                  coap_recv_cb);
        coap_service_register_uri(service_id, COAP_RSSI_URI,
                                      COAP_SERVICE_ACCESS_GET_ALLOWED,
                                      coap_recv_cb_rssi);
    #ifdef WISUN_TEST_METRICS
        coap_service_register_uri(service_id, COAP_TEST_METRICS_URI,
                                  COAP_SERVICE_ACCESS_GET_ALLOWED,
                                  coap_recv_cb_tstmetrics);
    #endif
    
    #ifdef COAP_PANID_LIST
        coap_service_register_uri(service_id, COAP_PANID_LIST_ALLOW_URI,
                                  COAP_SERVICE_ACCESS_GET_ALLOWED |
                                  COAP_SERVICE_ACCESS_PUT_ALLOWED |
                                  COAP_SERVICE_ACCESS_POST_ALLOWED,
                                  coap_panid_list_cb);
        coap_service_register_uri(service_id, COAP_PANID_LIST_DENY_URI,
                                  COAP_SERVICE_ACCESS_GET_ALLOWED |
                                  COAP_SERVICE_ACCESS_PUT_ALLOWED |
                                  COAP_SERVICE_ACCESS_POST_ALLOWED,
                                  coap_panid_list_cb);
        coap_service_register_uri(service_id, COAP_PANID_REDISCOVER_URI,
                                  COAP_SERVICE_ACCESS_PUT_ALLOWED |
                                  COAP_SERVICE_ACCESS_POST_ALLOWED,
                                  coap_panid_list_cb);
        coap_service_register_uri(service_id, COAP_PANID_LIST_BULK_URI,
                                  COAP_SERVICE_ACCESS_PUT_ALLOWED |
                                  COAP_SERVICE_ACCESS_POST_ALLOWED,
                                  coap_panid_list_cb);
    
        pan_rediscover_tasklet_start();
    #endif // COAP_PANID_LIST
    
    #if defined(COAP_OAD_ENABLE)
        // Initialize oad tasklet
        oad_tasklet_start();
    
        /* Initialize CoAP OAD services */
        coap_service_register_uri(service_id, OAD_FWV_REQ_URI,
                                  COAP_SERVICE_ACCESS_GET_ALLOWED |
                                  COAP_SERVICE_ACCESS_PUT_ALLOWED |
                                  COAP_SERVICE_ACCESS_POST_ALLOWED,
                                  coap_oad_cb);
        coap_service_register_uri(service_id, OAD_NOTIF_URI,
                                  COAP_SERVICE_ACCESS_GET_ALLOWED |
                                  COAP_SERVICE_ACCESS_PUT_ALLOWED |
                                  COAP_SERVICE_ACCESS_POST_ALLOWED,
                                  coap_oad_cb);
    #endif // COAP_OAD_ENABLE
    
    #else
        /* Convert string addr to ipaddr array */
        stoip6(multicast_addr_str, strlen(multicast_addr_str), multi_cast_addr);
    
        if(udpSocketSetup() == false)
        {
            tr_debug("Socket setup failed");
        }
    
        /* Set multicast send address */
        send_addr.type = ADDRESS_IPV6;
        send_addr.identifier = UDP_PORT;
        memcpy(send_addr.address, multi_cast_addr, 16);
    
        /* Set unicast send address */
        send_addr_unicast.type = ADDRESS_IPV6;
        send_addr_unicast.identifier = UDP_PORT_ECHO;
    
        while (1) {
            int16_t len;
            static uint32_t bcast_delay_count = 0;
            static uint32_t unicast_delay_count = 0;
            static bool prev_bcast_send = false;
            static bool prev_unicast_send = false;
            static ns_address_t source_addr = {0};
    
            if(prev_bcast_send != bcast_send)
            {
                if(bcast_send)
                {
                    tr_info("bcast_send enabled, sending light toggle every 10s");
                    bcast_delay_count = 100;
                }
                else
                {
                    tr_info("bcast_send disabled");
                }
            }
            prev_bcast_send = bcast_send;
    
            if(bcast_send)
            {
                if(bcast_delay_count==100)
                {
                    static bool light_state = false;
    
                    /* toggle light state */
                    light_state ^= 1;
                    /**
                    * Multicast control message is a NUL terminated string of semicolon separated
                    * <field identifier>:<value> pairs.
                    *
                    * Light control message format:
                    * t:lights;g:<group_id>;s:<1|0>;\0
                    */
                    snprintf(send_buf, sizeof(send_buf), "t:lights;g:%03d;s:%s;", MY_GROUP, (light_state ? "1" : "0"));
                    tr_debug("Sending lightcontrol message: %s", send_buf);
                    /* send every 20s */
                    tr_info("Sending multicast packet");
                    ret = socket_sendto(socket_id, &send_addr, send_buf, sizeof(send_buf));
                    tr_info("Sendto returned: %d", ret);
    
                    GPIO_toggle(CONFIG_GPIO_GLED);
    
                    bcast_delay_count = 0;
                }
                bcast_delay_count++;
            }
            if(unicast_send)
            {
                if(sent_dao == true) //&& !addr_ipv6_equal((const uint8_t*)root_unicast_addr, ns_in6addr_any))
                 {
                    memcpy(send_addr_unicast.address, root_unicast_addr, 16);
                    tr_info("Sending unicast packet from Router to Border Router");
                    ret = socket_sendto(socket_id, &send_addr_unicast, send_buf_unicast, sizeof(send_buf_unicast));
                    tr_info("Sendto returned: %d", ret);
                    unicast_send = false;
                 }
            }
    
            len = socket_recvfrom(socket_id, recv_buffer, sizeof(recv_buffer), 0, &source_addr);
            if(len > 0)
            {
                tr_info("Recv[%d]: %s, ", len, recv_buffer);
                handle_message((char*)recv_buffer);
            }
            else if(NS_EWOULDBLOCK != len)
            {
                tr_info("Recv error %x", len);
            }
    
            usleep(100000);
        }
    #endif /* endif for NWK_TEST not defined */
        nanostack_wait_till_connect();
    #if defined(COAP_SERVICE_ENABLE) && defined(COAP_PANID_LIST)
        pan_rediscover_update(PAN_REDISCOVER_JOIN_EVT);
    #endif
        return NULL;
    }
    
    #else //WISUN_NCP_ENABLE
    
    /*!
     * Signal NCP tasklet with the event NCP_SEND_RESPONSE_EVENT,
     * so that NCP_tasklet can process the sending of a response
     * back to the host, when the host sends a command.
     * e.g. Response to a command from host to set/get configuration.
     */
    void platformNcpSendRspSignal()
    {
        //post an event to ncp_tasklet
        arm_event_s event = {
               .sender = 0,
               .receiver = ncp_tasklet_id,
               .priority = ARM_LIB_HIGH_PRIORITY_EVENT,
               .event_type = NCP_SEND_RESPONSE_EVENT,
               .event_id = 0,
               .event_data = 0
           };
    
       eventOS_event_send(&event);
    }
    
    /*!
     * Signal NCP tasklet with the event NCP_SEND_ASYNC_RSPONSE_EVENT
     * so that NCP tasket can process the sending of an async response
     * back to the host - e.g. reception of a packet by the NWP
     */
    void platformNcpSendAsyncRspSignal()
    {
        //post an event to ncp_tasklet
        arm_event_s event = {
               .sender = 0,
               .receiver = ncp_tasklet_id,
               .priority = ARM_LIB_HIGH_PRIORITY_EVENT,
               .event_type = NCP_SEND_ASYNC_RSPONSE_EVENT,
               .event_id = 0,
               .event_data = 0
           };
    
       eventOS_event_send(&event);
    }
    
    /*!
     * Callback from the UART module indicating need for processing.
     */
    void platformUartSignal(uintptr_t arg)
    {
        //post an event to ncp_tasklet
        arm_event_s event = {
               .sender = 0,
               .receiver = ncp_tasklet_id,
               .priority = ARM_LIB_HIGH_PRIORITY_EVENT,
               .event_type = NCP_UART_EVENT,
               .event_id = 0,
               .event_data = arg
           };
    
       eventOS_event_send(&event);
    }
    
    #ifdef WISUN_AUTO_START
    /*!
     * Blink Leds continuously when an assert occurs
     */
    static inline void auto_start_assert_led()
    {
        while(1)
        {
            sleep(2);
            GPIO_toggle(CONFIG_GPIO_GLED);
            GPIO_toggle(CONFIG_GPIO_RLED);
        }
    }
    
    /*!
     * Post event to NCP tasklet to do net interface configuration
     * and start wisun stack without having to receive commands on
     * the NCP interface
     */
    static inline void autoStartSignal()
    {
        arm_event_s auto_event = {
                               .sender = 0,
                               .receiver = ncp_tasklet_id,
                               .priority = ARM_LIB_HIGH_PRIORITY_EVENT,
                               .event_type = NCP_AUTO_START_EVENT,
                               .event_id = 0,
                               .event_data = 0
                             };
    
        eventOS_event_send(&auto_event);
    }
    #endif //WISUN_AUTO_START
    
    /*!
     * Core logic for NCP tasklet. Helps process incoming, outgoing
     * messages on NCP interface based on the received event
     */
    void ncp_tasklet(arm_event_s *event)
    {
        arm_library_event_type_e event_type;
        event_type = (arm_library_event_type_e)event->event_type;
    
        switch (event_type)
        {
                case ARM_LIB_TASKLET_INIT_EVENT:
    
                    ncp_tasklet_id = event->receiver;
                    OtStack_instance = otInstanceInitSingle();
                    assert(OtStack_instance);
                    otNcpInit(OtStack_instance);
    
                    GPIO_write(CONFIG_GPIO_RLED, 1);
    
                    for(int i = 0; i< 3; i++)
                    {
                        GPIO_toggle(CONFIG_GPIO_GLED);
                        usleep(300000);
                    }
    
    #ifdef WISUN_AUTO_START
                    //post an event to ncp_tasklet
                    autoStartSignal();
    #endif //WISUN_AUTO_START
                    break;
    
    #ifdef WISUN_AUTO_START
                case NCP_AUTO_START_EVENT:
    
                    GPIO_write(CONFIG_GPIO_RLED, 1);
    
                    /* Automatically  bring the interface up & initiate Joining */
                    /*  Equivalent to running ifconfig up and wisunstack start by default at startup */
                    if(nanostack_net_if_up() != OT_ERROR_NONE)
                    {
                        //assert here and blink leds in loop
                        auto_start_assert_led();
                    }
                    if(nanostack_net_stack_up() != OT_ERROR_NONE)
                    {
                        //assert here and blink leds in loop
                        auto_start_assert_led();
                    }
                    break;
    #endif //WISUN_AUTO_START
    
                case NCP_UART_EVENT:
                    platformUartProcess(event->event_data);
                    break;
    
                case NCP_SEND_RESPONSE_EVENT:
                    platformNcpSendProcess();
                    break;
    
                case NCP_SEND_ASYNC_RSPONSE_EVENT:
                    platformNcpSendAsyncProcess();
                    break;
    
                default:
                    break;
    
         } //end of switch case
    }
    
    /*!
     * Create the NCP tasklet whose core logic is defined in ncp_tasklet()
     * Also, post the event to initialize it after creating the tasklet
     */
    void ncp_tasklet_start(void)
    {
            eventOS_event_handler_create(
            &ncp_tasklet,
            ARM_LIB_TASKLET_INIT_EVENT);
    }
    
    
    /* Below functions are invoked on receiving
     * the appropriate commands from host */
    
    /*!
     * Check if bringing up of network interface is done or not
     * return value: true if already done, false if not
     */
    bool is_net_if_up(void)
    {
        if(interface_id < 0)
        {
            return (false);
        }
        else
        {
            return (true);
        }
    }
    
    /*!
     * Bring up the network interface
     */
    otError nanostack_net_if_up(void)
    {
        if( MESH_ERROR_NONE != nanostack_wisunInterface_configure())
        {
            return (OT_ERROR_FAILED);
        }
        if (MESH_ERROR_NONE != nanostack_wisunInterface_bringup())
        {
            return (OT_ERROR_FAILED);
        }
    
        //everything is ok; return error_none
        return(OT_ERROR_NONE);
    
    }
    
    /*!
     * Check if the node joined the network or not on starting
     * the connection to network
     * return value: true if connected, false if not or in the
     * process of connecting
     */
    bool is_net_stack_up(void)
    {
        return(connectedFlg);
    }
    
    /*!
     * Start the process to connect node to the network
     */
    otError nanostack_net_stack_up(void)
    {
        GPIO_write(CONFIG_GPIO_RLED, 0);
    
        if(MESH_ERROR_NONE != nanostack_wisunInterface_connect(true))
        {
            return (OT_ERROR_FAILED);
        }
        //everything is ok; return error_none
        return(OT_ERROR_NONE);
    
        // it is expected that host will keep monitoring
        // if connection succeeded or not by giving
        // command to check if is_net_stack_up()
        // or reading current_net_state()
    }
    
    /*!
     * Helper function - returns the first non-zero channel from a list of
     * channels that is sent as input.
     */
    uint8_t get_first_fixed_channel(uint8_t * channel_list)
    {
        uint8_t idx, sizeOfChannelMask;
        uint8_t fixedChannelNum = 0;
        uint8_t bit_location = 0;
        uint8_t byteEntry = 0;
        sizeOfChannelMask = CHANNEL_BITMAP_SIZE;
    
        for(idx = 0; idx < sizeOfChannelMask; idx++)
        {
            byteEntry = channel_list[idx];
            bit_location = 0;
            while (bit_location < 8)
            {
                if (byteEntry & 0x01) {
                    return fixedChannelNum;
                }
                else {
                   fixedChannelNum++;
                }
    
                bit_location++;
                // shift byteEntry 1 to the right to pop off last bit
                byteEntry = byteEntry >> 1;
            }
        }
        return fixedChannelNum;
    }
    
    /*!
     * Helper function - returns the Link Local address of the node
     */
    if_address_entry_t *get_linkLocal_address(void)
    {
        protocol_interface_info_entry_t *cur;
        cur = protocol_stack_interface_info_get(IF_6LoWPAN);
    
        ns_list_foreach(if_address_entry_t, entry, &cur->ip_addresses )
        {
           if (entry->source == ADDR_SOURCE_UNKNOWN)
           {
               return entry;
           }
        }
    
        /* if we are here could not find the address */
        return NULL;
    
    }
    
    /*!
     * Helper function - returns the global unicast address of the node
     */
    if_address_entry_t *get_globalUnicast_address(void)
    {
        protocol_interface_info_entry_t *cur;
        cur = protocol_stack_interface_info_get(IF_6LoWPAN);
    
        ns_list_foreach(if_address_entry_t, entry, &cur->ip_addresses )
        {
           if (entry->source == ADDR_SOURCE_DHCP)
           {
               return entry;
           }
        }
    
        /* if we are here could not find the address */
        return NULL;
    }
    
    #endif //WISUN_NCP_ENABLE
    
    /*!
     * Helper function to get neighbor node metrics like rssi_in, rssi_out
     * Metrics are copied over to a global structure instance.
     */
    void fetch_neighbor_details()
    {
        protocol_interface_info_entry_t *cur;
        cur = protocol_stack_interface_info_get(IF_6LoWPAN);
        if(!cur || !cur->mac_parameters || !cur->mac_parameters->mac_neighbor_table)
        {
            tr_debug("fetch_neighbor_details: NULL pointer");
            return;
        }
    
        uint8_t max_nbrs, nbr_idx = 0;
    
        max_nbrs = cur->mac_parameters->mac_neighbor_table->list_total_size;
        cur_num_nbrs = (cur->mac_parameters->mac_neighbor_table->neighbour_list_size) - 1;
    
        for(uint8_t i = 0; i < max_nbrs; i++)
        {
            if(cur->mac_parameters->mac_neighbor_table->neighbor_entry_buffer[i].trusted_device == 1)
            {
                //found a valid neighbor
                //copy mac address
                memcpy(nbr_nodes_metrics[nbr_idx].mac_eui, cur->mac_parameters->mac_neighbor_table->neighbor_entry_buffer[i].mac64, sizeof(sAddrExt_t));
    
                //fetch and copy rssi_in rssi_out (Shift back the WS_RSL_SCALING amount)
                nbr_nodes_metrics[nbr_idx].rssi_in = cur->ws_info->neighbor_storage.neigh_info_list[i].rsl_in >> WS_RSL_SCALING;
                nbr_nodes_metrics[nbr_idx].rssi_out = cur->ws_info->neighbor_storage.neigh_info_list[i].rsl_out >> WS_RSL_SCALING;
    
                nbr_idx++;
    
                if(nbr_idx == cur_num_nbrs)
                {
                    // found all entries
                    break;
                }//end of inner if
    
            } //end of outer if
    
        }//end of for
    }
    
    /*!
     * Returns the current state of the node based on
     * at what state is the node during the network joining process
     * Return value used to vary the rate of red led blinking
     * while the node is joining the network
     */
    uint8_t get_current_net_state(void)
    {
        protocol_interface_info_entry_t *cur;
        cur = protocol_stack_interface_info_get(IF_6LoWPAN);
    
        uint8_t curNetState = 0;
    
        switch(cur->nwk_bootstrap_state)
        {
            case ER_IDLE:
                curNetState = 0;
                break;
            case ER_ACTIVE_SCAN:
                curNetState = 1;
                break;
            case ER_PANA_AUTH:
                curNetState = 2;
                break;
            case ER_SCAN:
                curNetState = 3;
                break;
            case ER_RPL_SCAN:
                curNetState = 4;
                break;
            case ER_BOOTSRAP_DONE:
                curNetState = 5;
                break;
            case ER_WAIT_RESTART:
                curNetState = 6;
                break;
            default:
                break;
        }
    
        return(curNetState);
    }
    
    /*!
     * Helper function - get current multicast groups a node is subscribed to
     */
    if_group_list_t *get_multicast_ip_groups()
    {
        protocol_interface_info_entry_t *cur;
        cur = protocol_stack_interface_info_get(IF_6LoWPAN);
    
        return &cur->ip_groups;
    }
    
    /*!
     * Helper function - add multicast address
     * return 0 if successful or -1 when an error occurs
     */
    int8_t add_multicast_addr(const uint8_t *address)
    {
        protocol_interface_info_entry_t *cur;
    
        cur = protocol_stack_interface_info_get(IF_6LoWPAN);
        if (!addr_is_ipv6_multicast(address) ||
            addr_ipv6_multicast_scope(address) <= IPV6_SCOPE_REALM_LOCAL)
        {
            return -1; // Non-multicast or realm/link/interface local scope address
        }
    
        if (addr_add_group(cur, address) == NULL)
        {
            return -1; // Address group add failed
        }
        return 0;
    }
    
    /*!
     * Helper function - remove multicast address
     * return 0 if successful or -1 when an error occurs
     */
    int8_t remove_multicast_addr(const uint8_t *address)
    {
        protocol_interface_info_entry_t *cur;
    
        cur = protocol_stack_interface_info_get(IF_6LoWPAN);
        if (!addr_is_ipv6_multicast(address) ||
            addr_ipv6_multicast_scope(address) <= IPV6_SCOPE_REALM_LOCAL)
        {
            return -1; // Non-multicast or realm/link/interface local scope address
        }
    
        addr_remove_group(cur, address);
        return 0;
    }
    
    rpl_instance_t *get_rpl_instance()
    {
        protocol_interface_info_entry_t *cur;
        cur = protocol_stack_interface_info_get(IF_6LoWPAN);
        if (!cur || !cur->rpl_domain) {
            return NULL;
        }
    
        rpl_instance_t *instance = ns_list_get_first(&cur->rpl_domain->instances);
    
        return instance;
    }
    
    rpl_dao_target_t *get_dao_target_from_addr(rpl_instance_t *instance, const uint8_t *addr)
    {
        // Always return NULL, only valid for BR devices
        return NULL;
    }
    
    uint16_t get_network_panid(void)
    {
        protocol_interface_info_entry_t *cur;
        cur = protocol_stack_interface_info_get(IF_6LoWPAN);
        if (!cur || !cur->ws_info) {
            return 0xFFFF;
        }
        return cur->ws_info->network_pan_id;
    }
    
    
    #ifdef WISUN_TEST_METRICS
    extern JOIN_TIME_s node_join_time;
    
    /*
     * Get latest test metrics
     */
    void get_test_metrics(test_metrics_s *test_metrics)
    {
        rpl_instance_t *rpl_inst;
    
        test_metrics->revision = 4;
        // Populate join time
        memcpy(&test_metrics->join_time, &node_join_time, sizeof(JOIN_TIME_s));
    
        // Populate MAC debug
        timac_getMACDebugCounts(&test_metrics->mac_debug);
    
        // Populate heap debug
        const mem_stat_t *heap_stats = ns_dyn_mem_get_mem_stat();
    
        test_metrics->heap_debug.heap_sector_size = heap_stats->heap_sector_size;
        test_metrics->heap_debug.heap_sector_allocated_bytes =
                heap_stats->heap_sector_allocated_bytes;
        test_metrics->heap_debug.heap_sector_allocated_bytes_max =
                heap_stats->heap_sector_allocated_bytes_max;
    
        timac_getMACPerfData(&test_metrics->mac_perf_data);
        test_metrics->udpPktCnt = num_pkts;
        // Reset UDP packet count to 0 for next test
        num_pkts = 0;
    
        // Get current rank
        rpl_inst = get_rpl_instance();
        test_metrics->current_rank = rpl_inst->current_rank;
    
        // Populate length
        test_metrics->length = (uint16_t) sizeof(test_metrics_s);
    }
    #endif
    


    And my PySpinel code:
    #!/usr/bin/env python3
    #
    #  Copyright (c) 2016-2019, The OpenThread Authors.
    #  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.
    #
    """
    Shell tool for controlling Wi-SUN NCP instances.
    """
    
    import os
    import sys
    import time
    import traceback
    import random
    import importlib
    import datetime
    import json
    
    import optparse
    
    import binascii
    import socket
    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.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
    import spinel_wisun_utils as wisun_util
    
    __copyright__ = "Copyright (c) 2016 The OpenThread Authors. Modified by Texas Instruments for TI NCP Wi-SUN devices"
    __version__ = "0.1.0"
    
    MASTER_PROMPT = "spinel-cli"
    
    import io
    import spinel.ipv6 as ipv6
    import spinel.common as common
    
    DEFAULT_BAUDRATE = 115200
    IPV6_ADDR_LEN    = 16
    
    # Platform type constants
    PLATFORM_TYPE_CC1312R7 = "CC1312R7"
    PLATFORM_TYPE_CC1352P7 = "CC1352P7"
    PLATFORM_TYPE_CC1314R10 = "CC1314R10"
    PLATFORM_TYPE_CC1354P10 = "CC1354P10"
    
    # Platform type mapping
    PLATFORM_CHIP_TYPE_LOOKUP = {
        PLATFORM_TYPE_CC1312R7: 23,
        PLATFORM_TYPE_CC1352P7: 26,
        PLATFORM_TYPE_CC1314R10: 30,
        PLATFORM_TYPE_CC1354P10: 31,
    }
    
    # UDP constants
    UDP_PORT     = 49153
    
    # Test Metrics Data Index
    TEST_METRICS_FILE_NAME = "test_metrics.csv"
    TEST_METRICS_BR_FILE_NAME = "test_metrics_br.csv"
    TEST_METRICS_VER1_PAYLOAD_LEN  = 44
    TEST_METRICS_VER2_PAYLOAD_LEN  = 108
    TEST_METRICS_REVISION_LEN = 2
    TEST_METRICS_LENGTH_SIZE  = 2
    TEST_METRICS_NUM_OF_JOIN_TIMES    = 5
    TEST_METRICS_JOIN_TIME_LEN        = 4 * TEST_METRICS_NUM_OF_JOIN_TIMES
    TEST_METRICS_NUMOF_BR_DISCONNECT_LEN = 4
    TEST_METRICS_DEV_TABLE_SIZE_LEN   = 2
    TEST_METRICS_FH_NT_NUM_NODES_LEN  = 2
    TEST_METRICS_HEAP_SIZE_LEN        = 4
    TEST_METRICS_CURRENT_HEAP_USE_LEN = 4
    TEST_METRICS_HIGH_HEAP_USE_LEN    = 4
    # MAC Performance Data
    TEST_METRICS_MAC_NUM_ASYNC_REQ    = 4
    TEST_METRICS_MAC_ASYNC_REQ_LEN    = 4 * TEST_METRICS_MAC_NUM_ASYNC_REQ
    TEST_METRICS_MAC_TX_BROADCAST_LEN = 4
    TEST_METRICS_MAC_TX_UNICAST_LEN   = 4
    # TX data confirm performance
    TEST_METRICS_MAC_TX_CONF_OK_LEN   = 4
    TEST_METRICS_MAC_TX_CONF_NOACK_LEN = 4
    TEST_METRICS_MAC_TX_CONF_NOENTRY_LEN = 4
    TEST_METRICS_MAC_TX_CONF_CHBUSY_LEN = 4
    TEST_METRICS_MAC_TX_CONF_OTHER_LEN = 4
    # RX indication
    TEST_METRICS_MAC_RX_NUM_ASYNC_REQ = 4
    TEST_METRICS_MAC_RX_ASYNC_IND_LEN = 4 * TEST_METRICS_MAC_RX_NUM_ASYNC_REQ
    TEST_METRICS_MAC_RX_IND_LEN       = 4
    # Num of UDP packets
    TEST_METRICS_NUM_UDP_PKTS_LEN     = 4
    # Current Rank for the Node
    TEST_METRICS_CURR_RANK_LEN        = 2
    
    TEST_METRICS_REVISION_INDEX = 0
    TEST_METRICS_LENGTH_INDEX              = TEST_METRICS_REVISION_INDEX + TEST_METRICS_REVISION_LEN
    TEST_METRICS_JOIN_TIME_INDEX           = TEST_METRICS_LENGTH_INDEX + TEST_METRICS_LENGTH_SIZE
    TEST_METRICS_NUMOF_BR_DISCONNECT_INDEX = TEST_METRICS_JOIN_TIME_INDEX + TEST_METRICS_JOIN_TIME_LEN
    TEST_METRICS_DEV_TABLE_SIZE_INDEX      = TEST_METRICS_NUMOF_BR_DISCONNECT_INDEX + TEST_METRICS_NUMOF_BR_DISCONNECT_LEN
    TEST_METRICS_FH_NT_NUM_NODES_INDEX     = TEST_METRICS_DEV_TABLE_SIZE_INDEX + TEST_METRICS_DEV_TABLE_SIZE_LEN
    TEST_METRICS_HEAP_SIZE_INDEX           = TEST_METRICS_FH_NT_NUM_NODES_INDEX + TEST_METRICS_FH_NT_NUM_NODES_LEN
    TEST_METRICS_CURRENT_HEAP_USE_INDEX    = TEST_METRICS_HEAP_SIZE_INDEX + TEST_METRICS_HEAP_SIZE_LEN
    TEST_METRICS_HIGH_HEAP_USE_INDEX       = TEST_METRICS_CURRENT_HEAP_USE_INDEX + TEST_METRICS_CURRENT_HEAP_USE_LEN
    # MAC Performance data
    TEST_METRICS_MAC_ASYNC_REQ_INDEX       = TEST_METRICS_HIGH_HEAP_USE_INDEX + TEST_METRICS_HIGH_HEAP_USE_LEN
    TEST_METRICS_MAC_TX_BROADCAST_INDEX    = TEST_METRICS_MAC_ASYNC_REQ_INDEX + TEST_METRICS_MAC_ASYNC_REQ_LEN
    TEST_METRICS_MAC_TX_UNICAST_INDEX      = TEST_METRICS_MAC_TX_BROADCAST_INDEX + TEST_METRICS_MAC_TX_BROADCAST_LEN
    TEST_METRICS_MAC_TX_CONF_OK_INDEX      = TEST_METRICS_MAC_TX_UNICAST_INDEX + TEST_METRICS_MAC_TX_UNICAST_LEN
    TEST_METRICS_MAC_TX_CONF_NOACK_INDEX   = TEST_METRICS_MAC_TX_CONF_OK_INDEX + TEST_METRICS_MAC_TX_CONF_OK_LEN
    TEST_METRICS_MAC_TX_CONF_NOENTRY_INDEX = TEST_METRICS_MAC_TX_CONF_NOACK_INDEX + TEST_METRICS_MAC_TX_CONF_NOACK_LEN
    TEST_METRICS_MAC_TX_CONF_CHBUSY_INDEX  = TEST_METRICS_MAC_TX_CONF_NOENTRY_INDEX + TEST_METRICS_MAC_TX_CONF_NOENTRY_LEN
    TEST_METRICS_MAC_TX_CONF_OTHER_INDEX   = TEST_METRICS_MAC_TX_CONF_CHBUSY_INDEX + TEST_METRICS_MAC_TX_CONF_CHBUSY_LEN
    TEST_METRICS_MAC_RX_ASYNC_IND_INDEX    = TEST_METRICS_MAC_TX_CONF_OTHER_INDEX + TEST_METRICS_MAC_TX_CONF_OTHER_LEN
    TEST_METRICS_MAC_RX_IND_INDEX          = TEST_METRICS_MAC_RX_ASYNC_IND_INDEX + TEST_METRICS_MAC_RX_ASYNC_IND_LEN
    TEST_METRICS_NUM_UDP_PKTS_INDEX        = TEST_METRICS_MAC_RX_IND_INDEX + TEST_METRICS_MAC_RX_IND_LEN
    TEST_METRICS_CURR_RANK_INDEX           = TEST_METRICS_NUM_UDP_PKTS_INDEX + TEST_METRICS_NUM_UDP_PKTS_LEN
    
    NODE_TYPE_BR   = 1
    NODE_TYPE_COAP = 2
    
    # COAP constants
    COAP_PORT    = 5683
    DEFAULT_TKL  = 8
    
    # COAP LED consts
    COAP_RLED_ID = 0
    COAP_GLED_ID = 1
    
    # COAP node join indication URI
    COAP_JOIN_URI = "join"
    
    # OAD consts
    MCUBOOT_VERSION_BYTE = 20
    OAD_REQ_URI_BASE  = "oad" # Shared base URI for OAD
    OAD_FWV_REQ_URI   = "fwv" # Full URI: oad/fwv
    OAD_NOTIF_REQ_URI = "ntf" # Full URI: oad/ntf
    OAD_BLOCK_REQ_URI = "img" # Full URI: oad/img
    OAD_ABORT_URI     = "abort" # Full URI: oad/abort
    
    OAD_IMG_ID        = 123
    OAD_COMPLETE_FLAG = 0xFFFF
    
    # PANID allow/denylist consts
    PANID_LIST_BASE_URI  = "panid"
    PANID_LIST_ALLOW_URI = "allow"
    PANID_LIST_DENY_URI  = "deny"
    PANID_LIST_BULK_URI  = "bulk"
    PAN_REDISCOVER_URI   = "rediscover"
    PANID_LIST_ACTION_ADD = 0
    PANID_LIST_ACTION_DEL = 1
    PANID_LIST_TIMEOUT_SEC = 1800
    
    COAP_UPDATE_NUM_CARS_URI = "upd_num_cars" # changed
    
    # COAP LED globals
    coap_led_req_token = None
    
    # COAP TESTMETRICS globals
    coap_testmetrics_req_token = None
    
    # OAD globals
    oad_file = None
    oad_img_len = 0
    oad_block_size = 0
    oad_start_time = 0
    oad_log_filename = 0
    oad_log_str = None
    oad_fwv_req_token = None
    oad_ntf_req_token = None
    
    # PANID allow/denylist globals
    panid_list_get_token = None
    panid_list_set_token = None
    panid_list_bulk_set_token_list = []
    pan_rediscover_req_token = None
    
    class IPv6Factory(object):
        ipv6_factory = ipv6.IPv6PacketFactory(
            ehf={
                0:
                    ipv6.HopByHopFactory(
                        hop_by_hop_options_factory=ipv6.HopByHopOptionsFactory(
                            options_factories={109: ipv6.MPLOptionFactory(), 99: ipv6.RPLOptionFactory()})),
                43:
                    ipv6.RoutingHeaderFactory(
                        routing_header_options_factory=ipv6.RoutingHeaderOptionsFactory(
                            options_factories={3: ipv6.SRHOptionFactory()}))
            },
            ulpf={
                17:
                    ipv6.UDPDatagramFactory(
                        udp_header_factory=ipv6.UDPHeaderFactory(), dst_port_factories={
                            COAP_PORT: ipv6.CoAPFactory(),
                            UDP_PORT: ipv6.UDPBytesPayloadFactory()
                        }
                    ),
                58:
                    ipv6.ICMPv6Factory(
                        body_factories={
                            128: ipv6.ICMPv6EchoBodyFactory(),
                            129: ipv6.ICMPv6EchoBodyFactory()
                        }
                    )
            })
    
        def __init__(self):
            self.seq_number = 0
            self.mpl_seq_number = 0
            self.coap_msg_id = 0
    
        def _any_identifier(self):
            return random.getrandbits(16)
    
        def _get_next_seq_number(self):
            curr_seq = self.seq_number
            self.seq_number += 1
            return curr_seq
    
        def _get_next_mpl_seq_number(self):
            curr_mpl_seq = self.mpl_seq_number
            self.mpl_seq_number += 1
            if (self.mpl_seq_number >= 256):
                self.mpl_seq_number = 0
            return curr_mpl_seq
    
        def _get_next_coap_msg_id(self):
            curr_coap_msg_id = self.coap_msg_id
            self.coap_msg_id += 1
            return curr_coap_msg_id
    
        def build_icmp_echo_request(self,
                                    src,
                                    dst,
                                    data,
                                    hop_limit=64,
                                    identifier=None,
                                    sequence_number=None):
            identifier = self._any_identifier() if identifier is None else identifier
            sequence_number = self._get_next_seq_number() if sequence_number is None else sequence_number
    
            _extension_headers = None
            if ipaddress.IPv6Address(dst).is_multicast:
                # Tunnel the IPv6 header + frame (containing the multicast address)
                _extension_headers = [ipv6.HopByHop(options=[
                                     ipv6.HopByHopOption(ipv6.HopByHopOptionHeader(_type=0x6d),
                                     ipv6.MPLOption(S=3, M=0, V=0, sequence=self._get_next_mpl_seq_number(),
                                     seed_id=ipaddress.ip_address(src).packed))])]
                _extension_headers.append(ipv6.IPv6Header(source_address=src,
                                                          destination_address=dst,
                                                          hop_limit=hop_limit))
                dst = "ff03::fc" # Use the realm-all-forwarders address for the outer ipv6 header
    
            ping_req = ipv6.IPv6Packet(
                ipv6_header=ipv6.IPv6Header(source_address=src,
                                            destination_address=dst,
                                            hop_limit=hop_limit),
                upper_layer_protocol=ipv6.ICMPv6(
                    header=ipv6.ICMPv6Header(_type=ipv6.ICMP_ECHO_REQUEST, code=0),
                    body=ipv6.ICMPv6EchoBody(identifier=identifier,
                                             sequence_number=sequence_number,
                                             data=data)),
                extension_headers=_extension_headers)
    
            return ping_req.to_bytes()
    
        def build_icmp_echo_response(self,
                                    src,
                                    dst,
                                    data,
                                    hop_limit=64,
                                    identifier=None,
                                    sequence_number=0):
            identifier = self._any_identifier() if identifier is None else identifier
    
            ping_req = ipv6.IPv6Packet(
                ipv6_header=ipv6.IPv6Header(source_address=src,
                                            destination_address=dst,
                                            hop_limit=hop_limit
                ),
                upper_layer_protocol=ipv6.ICMPv6(
                    header=ipv6.ICMPv6Header(_type=ipv6.ICMP_ECHO_RESPONSE, code=0),
                    body=ipv6.ICMPv6EchoBody(identifier=identifier,
                                             sequence_number=sequence_number,
                                             data=data
                    )
                )
            )
    
            return ping_req.to_bytes()
    
        def build_udp_request(self, src, dst, payload=None, hop_limit=64, msg_id=None,
                               tkl= 0, token=None, src_port=UDP_PORT, dst_port=UDP_PORT):
            _extension_headers = None
    
            if ipaddress.IPv6Address(dst).is_multicast:
                # Tunnel the IPv6 header + frame (containing the multicast address)
                _extension_headers = [ipv6.HopByHop(options=[
                                     ipv6.HopByHopOption(ipv6.HopByHopOptionHeader(_type=0x6d),
                                     ipv6.MPLOption(S=3, M=0, V=0, sequence=self._get_next_mpl_seq_number(),
                                     seed_id=ipaddress.ip_address(src).packed))])]
                _extension_headers.append(ipv6.IPv6Header(source_address=src,
                                                          destination_address=dst,
                                                          hop_limit=hop_limit))
                dst = "ff03::fc" # Use the realm-all-forwarders address for the outer ipv6 header
    
    
            udp_payload = ipv6.UDPBytesPayload(payload)
            udp_dgram = ipv6.IPv6Packet(
                ipv6_header=ipv6.IPv6Header(source_address=src,
                                            destination_address=dst,
                                            hop_limit=hop_limit
                ),
                upper_layer_protocol=ipv6.UDPDatagram(
                    header=ipv6.UDPHeader(src_port=src_port, dst_port=dst_port),
                    payload=udp_payload
                ),
                extension_headers=_extension_headers
            )
            return udp_dgram.to_bytes()
    
    
        def build_coap_request(self, src, dst, coap_type, coap_method_code, uri_path, option_list=None,
                               led_target=None, led_state=None, payload=None, hop_limit=64, msg_id=None,
                               tkl= 0, token=None):
            coap_options = []
    
            # Add and sort options
            if uri_path is not None:
                coap_options.append(ipv6.CoAPOption(ipv6.COAP_OPTION_URI_PATH, uri_path.encode('utf-8')))
            if option_list is not None:
                coap_options += option_list
            coap_options.sort(key=lambda option: option.option_num)
    
            # Build payload if necessary
            coap_payload = None
            if led_target is not None and led_state is not None:
                coap_payload = ipv6.CoAPPayload(bytes([led_target, led_state]))
            elif payload is not None:
                coap_payload = ipv6.CoAPPayload(bytes(payload))
    
            # Set msg_id if not specified
            if msg_id is None:
                msg_id = self._get_next_coap_msg_id()
    
            coap_request = ipv6.IPv6Packet(
                ipv6_header=ipv6.IPv6Header(source_address=src,
                                            destination_address=dst,
                                            hop_limit=hop_limit
                ),
                upper_layer_protocol=ipv6.UDPDatagram(
                    header=ipv6.UDPHeader(src_port=COAP_PORT, dst_port=COAP_PORT),
                    payload=ipv6.CoAP(
                        header=ipv6.CoAPHeader(_type=coap_type, tkl=tkl, code=coap_method_code,
                            msg_id=msg_id, token=token, options=coap_options
                        ),
                        payload=coap_payload
                    )
                )
            )
    
            return coap_request.to_bytes()
    
        def from_bytes(self, data):
            return self.ipv6_factory.parse(io.BytesIO(data), common.MessageInfo())
    
    class SpinelCliCmd(Cmd, SpinelCodec):
        """
        A command line shell for controlling OpenThread NCP nodes
        via the Spinel protocol.
        """
        VIRTUAL_TIME = os.getenv('VIRTUAL_TIME') == '1'
        # key is IP Address, values are listed below
        routing_table_dict = dict()
        """dict of routing table entries
                routing_table_dict["<IPv6>"]["prefixLen"]  = prefixLength
                routing_table_dict["<IPv6>"]["nextHopAddr"]  = IPv6address
                routing_table_dict["<IPv6>"]["lifetime"] = lifetime
        """
    
        ipv6_factory = IPv6Factory()
    
        def _get_routing_table(self):
            return self.routing_table_dict
    
        def _init_virtual_time(self):
            """
            compute addresses used for virtual time.
            """
            BASE_PORT = 9000
            MAX_NODES = 34
            PORT_OFFSET = int(os.getenv("PORT_OFFSET", "0"))
    
            self._addr = ('127.0.0.1', BASE_PORT * 2 + MAX_NODES * PORT_OFFSET)
            self._simulator_addr = ('127.0.0.1',
                                    BASE_PORT + MAX_NODES * PORT_OFFSET)
    
    
        # reset command, ifconfig, wisunstack start should clear the table
        # LAST_PROP_STATUS with a reason as RESET should also trigger clearing of routing table
        def clear_routing_table(self):
            self.routing_table_dict.clear()
    
        def __init__(self, stream, nodeid, vendor_module, *_a, **kw):
            if self.VIRTUAL_TIME:
                self._init_virtual_time()
            self.nodeid = nodeid
            self.tun_if = None
    
            self.wpan_api = WpanApi(stream, nodeid, vendor_module=vendor_module)
            self.wpan_api.queue_register(SPINEL.HEADER_DEFAULT)
    
            if kw.get('wpan_cb') is not None:
                self.wpan_callback = kw['wpan_cb']
                print("Changing callback function")
    
            self.wpan_api.callback_register(SPINEL.PROP_STREAM_NET,
                                            self.wpan_callback)
    
            self.wpan_api.callback_register(SPINEL.PROP_ROUTING_TABLE_UPDATE,
                                            self.wpan_routing_table_update_cb)
    
            # Default panid list json file
            # self.panid_list_json_path = "panid_list_example.json"
            self.panid_list_json_path = ""
            self.my_panid = self.prop_get_value(SPINEL.PROP_MAC_15_4_PANID)
    
            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
                if readline.__doc__ and 'libedit' in readline.__doc__:
                    readline.parse_and_bind("bind ^I rl_complete")
                else:
                    readline.parse_and_bind("tab: complete")
    
            # if hasattr(stream, 'pipe'):
            #     self.wpan_api.queue_wait_for_prop(SPINEL.PROP_LAST_STATUS,
            #                                      SPINEL.HEADER_ASYNC)
    
        command_names = [
            # Shell commands
            'exit',
            'quit',
            'clear',
            'history',
            #'debug',
            #'debug-mem',
            'v',
            'q',
    
            # Wi-SUN CLI commands
            'help',
    
            # properties in CORE category
            'protocolversion',
            'ncpversion',
            'interfacetype',
            'hwaddress',
            'trxfwversion',
            'fwversions',
    
            # properties in PHY category
            'ccathreshold',
            'txpower',
            'rssi',
    
            # properties in MAC category
            'panid',
    
            # properties in NET category
            'ifconfig',
            'wisunstack',
            'role',
            'networkname',
            'ping',
    
            # properties in TI Wi-SUN specific PHY category
            'region',
            'phymodeid',
            'unicastchlist',
            'broadcastchlist',
            'asyncchlist',
            'chspacing',
            'ch0centerfreq',
    
            # properties in TI Wi-SUN specific MAC category
            'ucdwellinterval',
            'bcdwellinterval',
            'bcinterval',
            'ucchfunction',
            'bcchfunction',
            'macfilterlist',
            'macfiltermode',
            'eapolallowlist',
    
            # properties in TI Wi-SUN specific NET category
            'routerstate',
            'dodagroute',
            'revokeDevice',
    
            # properties in IPV6 category
            'ipv6addresstable',
            'multicastlist',
            'udp',
            'coap',
            'numconnected',
            'connecteddevices',
    
            # Wi-SUN OAD
            'getoadfwver',
            'startoad',
            'getoadstatus',
    
            # PAN ID allow/denylist
            'setpanidlistjson',
            'getpanidlist',
            'setpanidlist',
            'panrediscover',
    
            # Enable direct mode
            "wisundirect",
    
            #reset cmd
            'reset',
            'nverase',
        ]
    
        @classmethod
        def update_routing_dict(self, value):
            t = time.localtime()
            print("Routing table update at " + time.asctime(t))
            try:
                changed_info, dst_ip_addr, routing_entry = wisun_util.parse_routingtable_property(value)
                print(changed_info)
                if changed_info == SPINEL.ROUTING_TABLE_ENTRY_DELETED:
                    # remove from list
                    if(self.routing_table_dict.get(dst_ip_addr) is not None):
                        # remove the specific routing table entry
                        self.routing_table_dict.pop(dst_ip_addr, None)
                elif changed_info == SPINEL.ROUTING_TABLE_ENTRY_CLEARED:
                    # Clear entire routing table
                    self.routing_table_dict.clear()
                else:
                    #add/update entry
                    self.routing_table_dict[dst_ip_addr] = routing_entry
    
            except RuntimeError:
                pass
    
        def get_dataFromPayload(self, payload, index, num_bytes):
            data = payload[index] + (payload[index+1] << 8)
            if (num_bytes == 4):
                data += (payload[index+2] << 16) + (payload[index+3] << 24)
            return data
    
        def parsePayLoadData(self, payload, nodeType):
            test_metrics_data = []
            index_offset = 0
            if nodeType == NODE_TYPE_BR:
                index_offset = 2
    
            # Version number (2 bytes)
            # print("Version:" + str(p.payload[TEST_METRICS_REVISION_INDEX])+str(p.payload[TEST_METRICS_REVISION_INDEX+1]))
            metrics_ver = self.get_dataFromPayload(payload, TEST_METRICS_REVISION_INDEX + index_offset, TEST_METRICS_REVISION_LEN)
            test_metrics_data.append(metrics_ver)
    
            # Data size
            # print("Data Size:" + str(p.payload[TEST_METRICS_LENGTH_INDEX+1]) + str(p.payload[TEST_METRICS_LENGTH_INDEX]))
            metrics_size = self.get_dataFromPayload(payload, TEST_METRICS_LENGTH_INDEX + index_offset, TEST_METRICS_LENGTH_SIZE)
    
            if (int(metrics_size) >= TEST_METRICS_VER1_PAYLOAD_LEN):
                # Array of joining time
                for index_mult in range (TEST_METRICS_NUM_OF_JOIN_TIMES):
                # Joining time (in ticks - 1 tick = 10uSec)
                    metrics_join_time = self.get_dataFromPayload(payload, (TEST_METRICS_JOIN_TIME_INDEX + index_offset + (4*index_mult)), 4)
                    test_metrics_data.append(metrics_join_time/100000)
    
                # Number of BR disconnect detected (4 bytes)
                metrics_numof_br_disconnect = self.get_dataFromPayload(payload, TEST_METRICS_NUMOF_BR_DISCONNECT_INDEX + index_offset, TEST_METRICS_NUMOF_BR_DISCONNECT_LEN)
                test_metrics_data.append(metrics_numof_br_disconnect)
    
                # Device Table Size (2 bytes)
                metrics_table_size = self.get_dataFromPayload(payload, TEST_METRICS_DEV_TABLE_SIZE_INDEX + index_offset, TEST_METRICS_DEV_TABLE_SIZE_LEN)
                test_metrics_data.append(metrics_table_size)
    
                # FH NT Num nodes (2 bytes)
                metrics_num_nodes = self.get_dataFromPayload(payload, TEST_METRICS_FH_NT_NUM_NODES_INDEX + index_offset, TEST_METRICS_FH_NT_NUM_NODES_LEN)
                test_metrics_data.append(metrics_num_nodes)
    
                # Heap Size (4 bytes)
                metrics_heap_size = self.get_dataFromPayload(payload, TEST_METRICS_HEAP_SIZE_INDEX + index_offset, TEST_METRICS_HEAP_SIZE_LEN)
                test_metrics_data.append(metrics_heap_size)
    
                # Current Heap Usage (4 bytes)
                metrics_curr_heap_usage = self.get_dataFromPayload(payload, TEST_METRICS_CURRENT_HEAP_USE_INDEX + index_offset, TEST_METRICS_CURRENT_HEAP_USE_LEN)
                test_metrics_data.append(metrics_curr_heap_usage)
    
                # Highest Heap Usage (4 bytes)
                metrics_high_heap_usage = self.get_dataFromPayload(payload, TEST_METRICS_HIGH_HEAP_USE_INDEX + index_offset, TEST_METRICS_HIGH_HEAP_USE_LEN)
                test_metrics_data.append(metrics_high_heap_usage)
    
            if (int(metrics_ver) > 1):
                # Array of Number of Async Channel Request
                for index_mult in range (TEST_METRICS_MAC_NUM_ASYNC_REQ):
                    metrics_mac_async_req = self.get_dataFromPayload(payload, (TEST_METRICS_MAC_ASYNC_REQ_INDEX + index_offset +(4*index_mult)), 4)
                    test_metrics_data.append(metrics_mac_async_req)
    
                # Number of TX broadcast (4 bytes)
                metrics_tx_broadcast = self.get_dataFromPayload(payload, TEST_METRICS_MAC_TX_BROADCAST_INDEX + index_offset, TEST_METRICS_MAC_TX_BROADCAST_LEN)
                test_metrics_data.append(metrics_tx_broadcast)
    
                # Number of TX Unicast (4 bytes)
                metrics_tx_unicast = self.get_dataFromPayload(payload, TEST_METRICS_MAC_TX_UNICAST_INDEX + index_offset, TEST_METRICS_MAC_TX_UNICAST_LEN)
                test_metrics_data.append(metrics_tx_unicast)
    
                # Number of TX Conf - OK (4 bytes)
                metrics_tx_conf_ok = self.get_dataFromPayload(payload, TEST_METRICS_MAC_TX_CONF_OK_INDEX + index_offset, TEST_METRICS_MAC_TX_CONF_OK_LEN)
                test_metrics_data.append(metrics_tx_conf_ok)
    
                # Number of TX Conf - NO ACK (4 bytes)
                metrics_tx_conf_noack = self.get_dataFromPayload(payload, TEST_METRICS_MAC_TX_CONF_NOACK_INDEX + index_offset, TEST_METRICS_MAC_TX_CONF_NOACK_LEN)
                test_metrics_data.append(metrics_tx_conf_noack)
    
                # Number of TX Conf - NO Entry (4 bytes)
                metrics_tx_conf_noentry = self.get_dataFromPayload(payload, TEST_METRICS_MAC_TX_CONF_NOENTRY_INDEX + index_offset, TEST_METRICS_MAC_TX_CONF_NOENTRY_LEN)
                test_metrics_data.append(metrics_tx_conf_noentry)
    
                # Number of TX Conf - Channel busy (4 bytes)
                metrics_tx_conf_ch_busy = self.get_dataFromPayload(payload, TEST_METRICS_MAC_TX_CONF_CHBUSY_INDEX + index_offset, TEST_METRICS_MAC_TX_CONF_CHBUSY_LEN)
                test_metrics_data.append(metrics_tx_conf_ch_busy)
    
                # Number of TX Conf - Other (4 bytes)
                metrics_tx_conf_other = self.get_dataFromPayload(payload, TEST_METRICS_MAC_TX_CONF_OTHER_INDEX + index_offset, TEST_METRICS_MAC_TX_CONF_OTHER_LEN)
                test_metrics_data.append(metrics_tx_conf_other)
    
                # Array of Number of Async Channel Indication
                for index_mult in range (TEST_METRICS_MAC_RX_NUM_ASYNC_REQ):
                    metrics_mac_async_ch_ind = self.get_dataFromPayload(payload, (TEST_METRICS_MAC_RX_ASYNC_IND_INDEX + index_offset + (4*index_mult)), 4)
                    test_metrics_data.append(metrics_mac_async_ch_ind)
    
                # Number of RX Indication (4 bytes)
                metrics_rx_ind = self.get_dataFromPayload(payload, TEST_METRICS_MAC_RX_IND_INDEX + index_offset, TEST_METRICS_MAC_RX_IND_LEN)
                test_metrics_data.append(metrics_rx_ind)
    
            if (int(metrics_ver) > 2):
                # Number of UDP Packets received (4 bytes)
                if nodeType == NODE_TYPE_COAP:
                    metrics_udp_pkts = self.get_dataFromPayload(payload, TEST_METRICS_NUM_UDP_PKTS_INDEX + index_offset, TEST_METRICS_NUM_UDP_PKTS_LEN)
                    test_metrics_data.append(metrics_udp_pkts)
    
            if (int(metrics_ver) > 3):
                # Node Rank / hop count (4 bytes)
                if nodeType == NODE_TYPE_COAP:
                    metrics_cur_rank = self.get_dataFromPayload(payload, TEST_METRICS_CURR_RANK_INDEX + index_offset, TEST_METRICS_CURR_RANK_LEN)
                    test_metrics_data.append(metrics_cur_rank)
    
            return test_metrics_data
    
        def saveTestMetrics (self, test_metrics_data, file_name):
            with open(file_name, "a") as test_data:
                for metric in range (len(test_metrics_data)):
                    test_data.write(f"{test_metrics_data[metric]}")
                    if metric < (len(test_metrics_data) - 1):
                        test_data.write(f",")
                test_data.write("\n")
    
        def wpan_callback(self, prop, value, tid):
            global oad_file
            global oad_start_time
            global oad_log_filename
            global oad_log_str
            global oad_block_size
            global panid_list_bulk_set_token_list
    
            consumed = False
            if prop == SPINEL.PROP_STREAM_NET:
                consumed = True
                try:
                    pkt = self.ipv6_factory.from_bytes(value)
                    if CONFIG.DEBUG_LOG_PKT:
                        CONFIG.LOGGER.debug(pkt)
                    if pkt.upper_layer_protocol.type == ipv6.IPV6_NEXT_HEADER_ICMP:
                        if pkt.upper_layer_protocol.header.type == ipv6.ICMP_ECHO_REQUEST:
                            print("\nEcho request: %d bytes from %s to %s, icmp_seq=%d hlim=%d. Sending echo response." %
                                  (len(pkt.upper_layer_protocol.body.data),
                                   pkt.ipv6_header.source_address,
                                   pkt.ipv6_header.destination_address,
                                   pkt.upper_layer_protocol.body.sequence_number,
                                   pkt.ipv6_header.hop_limit))
                            # Generate echo response
                            ping_resp = self.ipv6_factory.build_icmp_echo_response(
                                src=pkt.ipv6_header.destination_address,
                                dst=pkt.ipv6_header.source_address,
                                data=pkt.upper_layer_protocol.body.data,
                                identifier=pkt.upper_layer_protocol.body.identifier,
                                sequence_number=pkt.upper_layer_protocol.body.sequence_number)
                            self.wpan_api.ip_send(ping_resp)
                            # Let handler print result
                        elif pkt.upper_layer_protocol.header.type == ipv6.ICMP_ECHO_RESPONSE:
                            timenow = int(round(time.time() * 1000)) & 0xFFFFFFFF
                            timestamp = (pkt.upper_layer_protocol.body.identifier << 16 |
                                         pkt.upper_layer_protocol.body.sequence_number)
                            timedelta = (timenow - timestamp)
                            print("\n%d bytes from %s: icmp_seq=%d hlim=%d time=%dms" %
                                  (len(pkt.upper_layer_protocol.body.data),
                                   pkt.ipv6_header.source_address,
                                   pkt.upper_layer_protocol.body.sequence_number,
                                   pkt.ipv6_header.hop_limit, timedelta))
                        else:
                            print("ICMP packet received")
                    elif pkt.upper_layer_protocol.type == ipv6.IPV6_NEXT_HEADER_UDP:
                        udp_pkt = pkt.upper_layer_protocol
                        if udp_pkt.header.dst_port == COAP_PORT:
                            coap_pkt = pkt.upper_layer_protocol.payload
                            h = coap_pkt.header
                            p = coap_pkt.payload
                            option_str = "CoAP options:"
                            for option in h.options:
                                option_str += " {} ({}): {},".format(ipv6.COAP_OPTION_NAME_LOOKUP[option.option_num],
                                                                     option.option_num, option.option_val)
                            option_str = option_str[:-1] # Strip last comma
    
                            # Identify coap packet type
                            coap_packet_type = None
                            option_path_count = False
                            option_path_type = None
                            if h.type == ipv6.COAP_TYPE_ACK:
                                if oad_fwv_req_token == h.token and h.code == ipv6.COAP_RSP_CODE_CONTENT:
                                    coap_packet_type = "OAD_FWV_RESP"
                                elif oad_ntf_req_token == h.token and h.code == ipv6.COAP_RSP_CODE_CHANGED:
                                    coap_packet_type = "OAD_NTF_RESP"
                                elif coap_led_req_token == h.token:
                                    if h.code == ipv6.COAP_RSP_CODE_CONTENT:
                                        coap_packet_type = "LED_GET_RESP"
                                    elif h.code == ipv6.COAP_RSP_CODE_CHANGED:
                                        coap_packet_type = "LED_SET_RESP"
                                elif coap_testmetrics_req_token == h.token:
                                    if h.code == ipv6.COAP_RSP_CODE_CONTENT:
                                        coap_packet_type = "TESTMETRICS_GET_RESP"
                                elif panid_list_get_token == h.token:
                                    coap_packet_type = "PANID_LIST_GET_RESP"
                                elif panid_list_set_token == h.token:
                                    coap_packet_type = "PANID_LIST_SET_RESP"
                                elif h.token in panid_list_bulk_set_token_list:
                                    panid_list_bulk_set_token_list.remove(h.token)
                                    coap_packet_type = "PANID_LIST_BULK_RESP"
                                elif pan_rediscover_req_token == h.token:
                                    coap_packet_type = "PAN_REDISCOVER_RESP"
                            else:
                                for option in h.options:
                                    if option.option_num == ipv6.COAP_OPTION_URI_PATH:
                                        if option.option_val == str.encode(OAD_REQ_URI_BASE):
                                            option_path_count = True
                                            option_path_type = OAD_REQ_URI_BASE
                                            continue
                                        elif option.option_val == str.encode(COAP_JOIN_URI):
                                            # Note: This is not a join request, but a join indication. The node has already joined the network.
                                            # The coap message type is a coap post request.
                                            coap_packet_type = "COAP_JOIN_REQ"
                                        elif option.option_val == str.encode(COAP_UPDATE_NUM_CARS_URI):
                                            # Handle ev charger CoAP message
                                            # The coap message type is a coap post request.
                                            coap_packet_type = "EV_CHARGER_NUM_CARS_UPDATE"
                                        else:
                                            print("Invalid coap option base URI")
                                            coap_packet_type = None
                                    if option_path_count:
                                        option_path_count = False
                                        if option_path_type == OAD_REQ_URI_BASE:
                                            if option.option_val == str.encode(OAD_BLOCK_REQ_URI):
                                                if h.type == ipv6.COAP_TYPE_CON and h.code == ipv6.COAP_METHOD_CODE_GET and len(p.payload) == 5:
                                                    coap_packet_type = "OAD_BLOCK_REQ"
                                                    break
                                            elif option.option_val == str.encode(OAD_ABORT_URI):
                                                if h.type == ipv6.COAP_TYPE_NON and h.code == ipv6.COAP_METHOD_CODE_POST:
                                                    coap_packet_type = "OAD_ABORT_REQ"
                                                    break
                                        option_path_type = None
    
                            # Print out coap packet info if not OAD block request
                            # Block request will use separate print formatting due to large number of print statements
                            if coap_packet_type != "OAD_BLOCK_REQ":
                                print("\nCoAP packet received from {}: type: {} ({}), token: {}, code: {}.{:02d} ({}), msg_id: {}".format(
                                         pkt.ipv6_header.source_address, h.type, ipv6.COAP_TYPE_NAME_LOOKUP[h.type],
                                         h.token, h.code[0], h.code[1], ipv6.COAP_CODE_NAME_LOOKUP[h.code], h.msg_id))
    
                            # Response cases for each coap packet type
                            # OAD firmware version response
                            if coap_packet_type == "OAD_FWV_RESP":
                                img_id = p.payload[0]
                                platform = p.payload[1]
                                platform_str = None
                                try:
                                    platform_str = next(key for key, value in PLATFORM_CHIP_TYPE_LOOKUP.items() if value == platform)
                                except:
                                    platform_str = "Unknown"
                                version = [p.payload[2], p.payload[3], 0, 0]
                                version[2] = int.from_bytes(p.payload[4:6], "little")
                                version[3] = int.from_bytes(p.payload[6:10], "little")
                                print("Img ID: {}, Platform: {} ({}).".format(img_id, platform, platform_str))
                                print("OAD firmware version: {}.{}.{}.{}".format(version[0], version[1], version[2], version[3]))
                            # OAD notification response
                            elif coap_packet_type == "OAD_NTF_RESP":
                                print("OAD notification response received")
                                status = p.payload[1]
                                if status == 1:
                                    print("OAD upgrade accepted. Starting block transfer")
                                    # Start measuring OAD duration
                                    oad_start_time = time.time()
                                elif status == 0:
                                    print("OAD upgrade rejected")
                                    self.cleanup_oad()
                                else:
                                    print("Invalid OAD notification response status")
                            # OAD block request
                            elif coap_packet_type == "OAD_BLOCK_REQ":
                                # Get OAD logger
                                oad_logger = logging.getLogger('oad')
    
                                # Parse oad block request packet
                                oad_img_id = p.payload[0]
                                oad_block_num = int.from_bytes(p.payload[1:3], "little")
                                oad_total_blocks = int.from_bytes(p.payload[3:5], "little")
                                src_addr = pkt.ipv6_header.destination_address
                                dest_addr = pkt.ipv6_header.source_address
                                oad_block_num_bytes = list(oad_block_num.to_bytes(2, "little"))
    
                                # OAD block transfer end signal, close the file and ack the frame
                                if oad_block_num == OAD_COMPLETE_FLAG:
                                    oad_duration = time.time() - oad_start_time
                                    oad_duration = str(datetime.timedelta(seconds=oad_duration))
                                    print("\nOAD complete! Duration: {}".format(oad_duration))
                                    self.cleanup_oad()
                                    oad_log_str = None
                                    coap_req = self.ipv6_factory.build_coap_request(format(src_addr), format(dest_addr), ipv6.COAP_TYPE_ACK,
                                        ipv6.COAP_RSP_CODE_CONTENT, None, payload=None, msg_id=(h.msg_id+1), tkl=h.tkl, token=h.token)
                                    self.wpan_api.ip_send(coap_req)
                                    return
    
                                # Calculate OAD block start byte, block size, and OAD duration
                                oad_block_start = oad_block_num * oad_block_size
                                oad_block_end = oad_block_start + oad_block_size
                                if oad_block_end > oad_img_len:
                                    oad_block_end = oad_img_len
                                oad_block_size = oad_block_end - oad_block_start
                                oad_duration = time.time() - oad_start_time
                                oad_duration = str(datetime.timedelta(seconds=oad_duration))
                                oad_log_str = "Block {:04}/{:04} sent. Block size: {:03d}. Duration: {}".format(
                                        oad_block_num + 1, oad_total_blocks, oad_block_size, oad_duration)
                                oad_logger.info(oad_log_str)
    
                                # Read oad image from file
                                oad_payload = []
                                try:
                                    oad_file.seek(oad_block_start, os.SEEK_SET)
                                    for _ in range (0, oad_block_size):
                                        byte = oad_file.read(1)
                                        oad_payload.append(int.from_bytes(byte, 'big'))
                                except:
                                    print("\nError reading oad binary file")
                                    return False
    
                                # Construct and send OAD block payload
                                oad_payload = [oad_img_id] + oad_block_num_bytes + oad_payload
                                coap_req = self.ipv6_factory.build_coap_request(format(src_addr), format(dest_addr), ipv6.COAP_TYPE_ACK,
                                    ipv6.COAP_RSP_CODE_CONTENT, None, payload=oad_payload, msg_id=(h.msg_id+1), tkl=h.tkl, token=h.token)
                                self.wpan_api.ip_send(coap_req)
                            # OAD abort request
                            elif coap_packet_type == "OAD_ABORT_REQ":
                                self.cleanup_oad()
                                print("OAD aborted!")
                                oad_log_str = None
                                return
                            # LED GET response
                            elif coap_packet_type == "LED_GET_RESP":
                                print ("Payload size:" + str(len(p.payload)))
                                if len(p.payload) == 2:
                                    rled_state = "Off" if int(p.payload[0]) == 0 else "On"
                                    gled_state = "Off" if int(p.payload[1]) == 0 else "On"
                                    print("RLED state: {}, GLED state: {}".format(rled_state, gled_state))
                                else:
                                    print("Error: invalid LED state")
                                    print("Payload: {}".format(list(p.payload)))
                            # LED PUT/POST response
                            elif coap_packet_type == "LED_SET_RESP":
                                print("LED state successfully set")
                            elif coap_packet_type == "COAP_JOIN_REQ":
                                dest_addr = pkt.ipv6_header.source_address # Dest addr for coap allow/denylist set
                                src_addr  = pkt.ipv6_header.destination_address # Src addr (own ip addr, can be read from coap pkt)
                                print("CoAP node with address {} joined!".format(dest_addr))
    
                                # Read allow/deny list entry in json file
                                try:
                                    # JSON PANID list not used, send empty filter lists
                                    if self.panid_list_json_path is None or not os.path.isfile(self.panid_list_json_path):
                                        print("No PAN ID list JSON file found, no modifications to device PAN ID list")
                                        payload = [0] * 6
                                        panid_list_bulk_set_token_list.append(random.getrandbits(DEFAULT_TKL*8))
                                        coap_req = self.ipv6_factory.build_coap_request(src_addr, dest_addr, ipv6.COAP_TYPE_CON,
                                            ipv6.COAP_METHOD_CODE_PUT, PANID_LIST_BASE_URI + '/' + PANID_LIST_BULK_URI,
                                            payload=payload, tkl=DEFAULT_TKL, token=panid_list_bulk_set_token_list[-1])
                                        self.wpan_api.ip_send(coap_req)
                                        return
                                    # JSON PANID list used
                                    with open(self.panid_list_json_path) as fp:
                                        panid_json = json.load(fp)
                                        print("Setting coap node PAN ID list according to JSON file")
    
                                        # Read EUI provided in join coap request
                                        join_node_eui = list(p.payload)
                                        join_node_eui_str = "".join("{:02X}".format(i) for i in join_node_eui)
                                        allowlist = None
                                        denylist = None
                                        if join_node_eui_str in panid_json:
                                            # Convert string entries to int
                                            print("CoAP node EUI found in JSON, sending PAN ID list")
                                            allowlist = [int(entry, 16) for entry in panid_json[join_node_eui_str]["allow"]]
                                            denylist = [int(entry, 16) for entry in panid_json[join_node_eui_str]["deny"]]
                                        else:
                                            # If entry does not exist, it implies denylist with its own panid
                                            print("CoAP node EUI not found in JSON, sending default denylist containing current PAN ID")
                                            print("The node will try to join another PAN.")
                                            allowlist = []
                                            denylist = [self.my_panid]
    
                                        # Format of COAP JOIN RESP payload:
                                        # <2 byte allow/deny list timeout (seconds) +
                                        # <2 byte allowlist len> + <2 byte denylist len> +
                                        # <2 byte allowlist entry>*(allowlist len) + <2 byte denylist entry>*(denylist len)
                                        payload = [PANID_LIST_TIMEOUT_SEC & 0xFF, PANID_LIST_TIMEOUT_SEC >> 8]
                                        payload.extend([len(allowlist) & 0xFF, len(allowlist) >> 8])
                                        payload.extend([len(denylist) & 0xFF, len(denylist) >> 8])
                                        for panid in allowlist:
                                            payload.extend([panid & 0xFF, panid >> 8])
                                        for panid in denylist:
                                            payload.extend([panid & 0xFF, panid >> 8])
    
                                        # Send payload containing allow/deny list contents. Allow the coap node
                                        # to decide whether to perform pan rediscovery.
                                        # Generate a random token and save it for ACK usage later
                                        panid_list_bulk_set_token_list.append(random.getrandbits(DEFAULT_TKL*8))
                                        coap_req = self.ipv6_factory.build_coap_request(src_addr, dest_addr, ipv6.COAP_TYPE_CON,
                                            ipv6.COAP_METHOD_CODE_PUT, PANID_LIST_BASE_URI + '/' + PANID_LIST_BULK_URI,
                                            payload=payload, tkl=DEFAULT_TKL, token=panid_list_bulk_set_token_list[-1])
                                        self.wpan_api.ip_send(coap_req)
                                except:
                                    print("Failed to send JSON PAN IDs to coap node")
                                    print(traceback.format_exc())
                                    return False
                            elif coap_packet_type == "PANID_LIST_BULK_RESP":
                                if h.code == ipv6.COAP_RSP_CODE_CHANGED or h.code == ipv6.COAP_RSP_CODE_VALID:
                                    print("JSON file PAN IDs added to PAN ID list.")
                                    try:
                                        filter_json = None
                                        src_addr_str = str(pkt.ipv6_header.source_address)
                                        with open("./spinel_conn_dev_filter.json", "a+") as fp:
                                            fp.seek(0)
                                            try:
                                                filter_json = json.load(fp)
                                            except json.decoder.JSONDecodeError:
                                                filter_json = []
                                            if h.code == ipv6.COAP_RSP_CODE_CHANGED:
                                                print("PAN rediscovery started")
                                                if (src_addr_str not in filter_json):
                                                    filter_json.append(src_addr_str)
                                                print("Address {} added to connecteddevices filter".format(src_addr_str))
                                            else: # h.code == ipv6.COAP_RSP_CODE_VALID:
                                                print("PAN rediscovery not required, staying in network")
                                                filter_json[:] = [addr for addr in filter_json if addr != src_addr_str]
                                                print("Address {} removed from connecteddevices filter".format(src_addr_str))
                                        # Open in write mode to erase the previous contents and rewrite
                                        with open("./spinel_conn_dev_filter.json", "w") as fp:
                                            json.dump(filter_json, fp)
                                    except:
                                        print("Failed to save address to connected devices filter")
                                        print(traceback.format_exc())
                                else:
                                    print("Coap node failed to set JSON PAN IDs")
                            elif coap_packet_type == "PANID_LIST_GET_RESP":
                                list_size = int.from_bytes(p.payload[0:2], "little")
                                if list_size == 0:
                                    print("PAN ID list is empty!")
                                else:
                                    print("PAN ID list contents:")
                                    for i in range(list_size):
                                        print(hex(int.from_bytes(p.payload[(i*2)+2:(i*2)+4], "little")))
                            elif coap_packet_type == "PANID_LIST_SET_RESP":
                                if h.code == ipv6.COAP_RSP_CODE_CREATED:
                                    print("PAN ID successfully added to list")
                                elif h.code == ipv6.COAP_RSP_CODE_DELETED:
                                    print("PAN ID successfully removed from list")
                                else:
                                    print("Error setting PAN ID list")
                            elif(coap_packet_type == "PAN_REDISCOVER_RESP"):
                                print("PAN rediscover successfully triggered\n")
                            # METRICS GET response
                            elif coap_packet_type == "TESTMETRICS_GET_RESP":
                                test_metrics_data = []
                                print("Coap metrics - Len:" + str(len(p.payload)))
                                # print("Raw CoAP payload: {}".format(list(p.payload)))
                                if len(p.payload) > 1:
                                    test_metrics_data.append(pkt.ipv6_header.source_address)
                                    # Parse Payload data and add to the array
                                    test_metrics_data.extend(self.parsePayLoadData(p.payload, NODE_TYPE_COAP))
    
                                    # Write all test metrics data to file
                                    self.saveTestMetrics(test_metrics_data, TEST_METRICS_FILE_NAME)
                                else:
                                    print("Error: invalid Payload size for Test Metrics")
                                    print("Payload: {}".format(list(p.payload)))
                            elif coap_packet_type == "EV_CHARGER_NUM_CARS_UPDATE":
                                # Handle ev charger CoAP message
                                if h.code == ipv6.COAP_METHOD_CODE_POST:
                                    print("Payload length in bytes: {} \n".format(len(list(p.payload))))
                                    if h.type == ipv6.COAP_TYPE_CON:
                                        try:
                                            srcIPAddress = pkt.ipv6_header.destination_address
                                            # Send an acknowledgement to the CoAP Node
                                            coap_req = self.ipv6_factory.build_coap_request(format(srcIPAddress), pkt.ipv6_header.source_address, ipv6.COAP_TYPE_ACK,
                                                ipv6.COAP_RSP_CODE_CONTENT, None, payload=bytes("Hello World!", encoding="utf-8"), msg_id=(h.msg_id+1), tkl=h.tkl, token=h.token)
                                            self.wpan_api.ip_send(coap_req)
                                            print("Sent acknowledgement")
                                        except:
                                            print("Failed to send acknowledgement")
                                            print(traceback.format_exc())
                            else:
                                if len(p.payload) == 0:
                                    print("No CoAP payload")
                                else:
                                    print("Raw CoAP payload: {}".format(list(p.payload)))
                        else:
                            h = udp_pkt.header
                            p = udp_pkt.payload
                            payload = p.data.decode('utf-8')
                            print("UDP packet received")
                            print("UDP payload: {}".format(payload))
                    else:
                        print("\nReceived IPv6 packet with unsupported upper layer protocol (not UDP or ICMP)")
                except RuntimeError:
                    print("\n Incoming IPv6 Packet Decode Error")
                    print(traceback.format_exc())
                    pass
            return consumed
    
        @classmethod
        def wpan_routing_table_update_cb(cls, prop, value, tid):
            consumed = False
            if prop == SPINEL.PROP_ROUTING_TABLE_UPDATE:
                consumed = True
                cls.update_routing_dict(value)
    
            return consumed
    
        @classmethod
        def log(cls, text):
            """ Common log handler. """
            CONFIG.LOGGER.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
                    line += '\0'
                    value = line.encode('utf-8')
                elif mixed_format in (
                        'D',
                        'E'):  # Expect raw data to be hex string w/o delimeters
                    value = util.hex_to_bytes(line)
                elif isinstance(line, str):
                    # Most everything else is some type of integer
                    value = int(line, 0)
            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 in ('D', 'U', 'E'):
                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', output=True):
            """ 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 not output:
                return result
    
            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(binascii.hexlify(value).decode('utf8'))
                elif mixed_format == 'H':
                    if prop_id == SPINEL.PROP_MAC_15_4_PANID:
                        print("0x%04x" % value)
                    else:
                        print("%04x" % value)
                elif mixed_format == 'B' or mixed_format == 'L':
                    print(str(int.from_bytes(value, "little", signed=False)))
                else:
                    print(str(value))
    
            print("Done")
            return value
    
        def cleanup_oad(self):
            global oad_file
            global oad_file_name
    
            oad_logger = logging.getLogger('oad')
            if oad_file is not None and not oad_file.closed:
                oad_file.close()
            if oad_logger is not None:
                for handler in oad_logger.handlers:
                    if handler.baseFilename == oad_log_filename:
                        oad_logger.removeHandler(handler)
                        handler.close()
    
        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] == "#":
                CONFIG.LOGGER.debug(line)
            else:
                CONFIG.LOGGER.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)
    
    
        # Wi-SUN CLI commands
    
        def do_testcommand(self, line):
            """
            testcommand
                For internal testing purposes only.
    
                testcommand <test command type> [test command parameters]
    
                EDFE testing example:
                > testcommand edfe on
                Turn EDFE mode on
    
                > testcommand edfe off
                Turn EDFE mode off
    
                VPIE enable example:
                > testcommand vpie on
                Turn VPIE on
    
                > testcommand vpie off
                Turn VPIE off
    
                MACMPL test enable command
                >testcommand macmpl on
                Turn MACMPL test. The BR will generate the MPL traffic every one second
    
                >testcommand macmpl off
                Trun MACMPL test off
            """
            params = line.split(" ")
            if params[0] == "edfe":
                if len(params) == 1:
                    value = self.prop_get_value(SPINEL.PROP_TEST_COMMAND)
                    if value != None:
                        map_arg_value = {
                            0: "off",
                            1: "on",
                        }
                        print("EDFE " + map_arg_value[value])
                elif len(params) == 2:
                    if params[1] == "on":
                        print("Turn EDFE mode on")
                        self.prop_set(SPINEL.PROP_TEST_COMMAND, '1')
                    elif params[1] == "off":
                        print("Turn EDFE mode off")
                        self.prop_set(SPINEL.PROP_TEST_COMMAND, '0')
                else:
                    print("Invalid number of parameters")
            elif params[0] == "vpie":
                if len(params) == 1:
                    value = self.prop_get_value(SPINEL.PROP_VPIE_COMMAND)
                    if value != None:
                        map_arg_value = {
                            0: "off",
                            1: "on",
                        }
                        print("VPIE " + map_arg_value[value])
                elif len(params) == 2:
                    if params[1] == "on":
                        print("Turn VPIE on")
                        self.prop_set(SPINEL.PROP_VPIE_COMMAND, '1')
                    elif params[1] == "off":
                        print("Turn VPIE off")
                        self.prop_set(SPINEL.PROP_VPIE_COMMAND, '0')
                    else :
                        print("The command parameter is wrong. Please use on/off")
                else:
                    print("Invalid number of parameters")
            elif params[0] == "macmpl":
                if len(params) == 1:
                    value = self.prop_get_value(SPINEL.PROP_MACMPL_COMMAND)
                    if value != None:
                        map_arg_value = {
                            0: "off",
                            1: "on",
                        }
                        print("MACMPL " + map_arg_value[value])
                elif len(params) == 2:
                    if params[1] == "on":
                        print("Turn MACMPL on")
                        self.prop_set(SPINEL.PROP_MACMPL_COMMAND, '1')
                    elif params[1] == "off":
                        print("Turn MACMPL off")
                        self.prop_set(SPINEL.PROP_MACMPL_COMMAND, '0')
                    else :
                        print("The command parameter is wrong. Please use on/off")
                else:
                    print("Invalid number of parameters")
            elif params[0] == "jtime":
                if len(params) == 2:
                    # Set PanID and start wisun Stack
                    print ("Panid: " + params[1])
                    self.handle_property(params[1], SPINEL.PROP_MAC_15_4_PANID, 'H', output=False)
                    # self.my_panid = self.prop_get_value(SPINEL.PROP_MAC_15_4_PANID)
                    self.prop_set(SPINEL.PROP_NET_IF_UP, '1', output=False)
                    result = self.prop_get_or_set_value(SPINEL.PROP_NET_STACK_UP, "1")
                    if result != None:
                        t = time.localtime()
                        print("Done at " + time.asctime(t))
                    else:
                        print("Error")
                else:
                    print("Invalid number of parameters")
    
        def do_wisundirect(self, line):
            """
            wisundirect <on/off>
                Turn Wi-SUN direct mode on. Off by default. Only intended to be used by Border Router devices.
                This enables a specific message header, allowing link-local unicast messages from the Border Router
                to be TX'ed and RX'ed properly by devices that are not fully joined. Both devices must have security
                exchange disabled (use preshared keys). wisundirect command should be called before interface/stack up.
    
                After calling wisundirect on, use ping or coap commands with a MAC address in the <address> field to
                send packets to a specific HW MAC address. Both devices must have exchanged at least one
                PAN advertisement solicit/PAN advertisement for this ping/coap command to go through.
    
                Note that after enabling wisundirect on Border Router, PAN Configuration will no longer be transmitted.
                This is to ensure consistent bootstrap state for joining nodes.
                Examples:
                    > wisundirect
                    Wi-SUN direct mode off
    
                    > wisundirect on
                    Turn Wi-SUN direct mode on
            """
            params = line.split(" ")
            if params[0] == "":
                value = self.prop_get_value(SPINEL.PROP_VPIE_COMMAND)
                if value != None:
                    map_arg_value = {
                        0: "off",
                        1: "on",
                    }
                    print("Wi-SUN direct mode " + map_arg_value[value])
                else:
                    print("Could not get Wi-SUN direct mode")
            elif len(params) == 1:
                if params[0] == "on":
                    print("Turn Wi-SUN direct mode on")
                    self.prop_set(SPINEL.PROP_VPIE_COMMAND, '1')
                elif params[0] == "off":
                    print("Turn Wi-SUN direct mode off")
                    self.prop_set(SPINEL.PROP_VPIE_COMMAND, '0')
                else :
                    print("The command parameter is wrong. Please use on/off")
            else:
                print("Invalid number of parameters")
    
        # for Core properties
        def do_protocolversion(self, line):
            """
            protocol version
    
                Print the protocol version information: Major and Minor version number.
    
                > protocolversion
                1.0
                Done
            """
            #self.handle_property(line, SPINEL.PROP_PROTOCOL_VERSION, 'ii')
            self.handle_property(line, SPINEL.PROP_PROTOCOL_VERSION, 'U')
    
        def do_ncpversion(self, line):
            """
            ncp version
    
                Print the build version information.
    
                > ncpversion
                TIWISUNFAN/1.0.1; DEBUG; Feb 7 2021 18:22:04
                Done
            """
            self.handle_property(line, SPINEL.PROP_NCP_VERSION, 'U')
    
    
        def do_interfacetype(self, line):
            """
            Interface type
    
                Identifies the network protocol for the NCP . Will always return 4 (Wi-SUN FAN)
    
                > interfacetype
                4
                Done
            """
            self.handle_property(line, SPINEL.PROP_INTERFACE_TYPE, 'i')
    
    
        def do_hwaddress(self, line):
            """
            hwaddress
    
                Get the IEEE 802.15.4 Extended Address.
    
                > hwaddress
                dead00beef00cafe
                Done
    
            """
            self.handle_property(line, SPINEL.PROP_HWADDR, 'E')
    
        def do_trxfwversion(self, line):
            """
            trxfwversion
    
                Get the TRX FW version.
    
                > trxfwversion
                0.11.0.22.3316673118
                Done
    
            """
            value = self.prop_get_value(SPINEL.PROP_TRXFWVER)
            major = int.from_bytes(value[:1], "little", signed=False)
            minor = int.from_bytes(value[1:2], "little", signed=False)
            patch = int.from_bytes(value[2:3], "little", signed=False)
            build = int.from_bytes(value[3:4], "little", signed=False)
            bhash = int.from_bytes(value[4:8], "little", signed=False)
            print(str(major) + "." + str(minor) + "." + str(patch) + "." + str(build) + "."+ str(bhash))
            print("Done")
    
        def do_fwversions(self, line):
            """
            fwversions
    
                Get the TRX FW version and the TI Wi-SUN stack version.
    
                > versions
                TRX FW VERSION: 0.11.0.22.3316673118
                TIWISUNFAN/1.0.1; RELEASE; Feb 7 2021 18:22:04
                Done
    
            """
            value = self.prop_get_value(SPINEL.PROP_TRXFWVER)
            major = int.from_bytes(value[:1], "little", signed=False)
            minor = int.from_bytes(value[1:2], "little", signed=False)
            patch = int.from_bytes(value[2:3], "little", signed=False)
            build = int.from_bytes(value[3:4], "little", signed=False)
            bhash = int.from_bytes(value[4:8], "little", signed=False)
            print("TRX FW VERSION: " + str(major) + "." + str(minor) + "." + str(patch) + "." + str(build) + "."+ str(bhash))
            self.handle_property(line, SPINEL.PROP_NCP_VERSION, 'U')
    
    
        # for PHY properties
        def do_ccathreshold(self, line):
            """
            ccathreshold
    
                Get the CCA ED Threshold in dBm.
    
                > ccathreshold
                -10
                Done
    
            ccathreshold <ccathreshold>
    
                Set the CCA ED Threshold in dBm.
    
                > ccathreshold -70
                Done
            """
            self.handle_property(line, SPINEL.PROP_PHY_CCA_THRESHOLD, mixed_format='b')
    
    
        def do_txpower(self, line):
            """
            txpower
    
                Get the transmit power in dBm.
    
                > txpower
                0
                Done
    
            txpower <txpower>
    
                Set the transmit power in dBm.
    
                > txpower -10
                Done
            """
            self.handle_property(line, SPINEL.PROP_PHY_TX_POWER, mixed_format='b')
    
        def do_rssi(self, line):
            """
            rssi
    
                Get the rssi_in and rssi_out values of the neighbor nodes.
    
                > rssi
                Number of Neighbor Nodes = 2
                Neighbor Node RSSI metrics are (EUI, RSSI_IN, RSSI_OUT):
                00124b001ca19463, -22.0dBm, -23.0dBm
                00124b001ca13486, -32.0dBm, -33.0dBm
            """
            num_nbrs = self.prop_get_value(SPINEL.PROP_PHY_NUM_NBRS)
            print("Number of Neighbor Nodes = " + str(num_nbrs))
            print("Neighbor Node RSSI metrics are (EUI, RSSI_IN, RSSI_OUT): ")
    
            for i in range(0,num_nbrs):
                nbr_metric = self.prop_get_value(SPINEL.PROP_PHY_NBR_METRICS)
                #print(nbr_metric)
                rssi_in = int.from_bytes(nbr_metric[8:10], "little", signed=False)
                rssi_out = int.from_bytes(nbr_metric[10:12], "little", signed=False)
                rssi_in_dBm = (rssi_in) - 174
                rssi_out_dBm = (rssi_out) - 174
                print(binascii.hexlify(nbr_metric[:8]).decode('utf8') + ", " + str(rssi_in_dBm) + "dBm, " + str(rssi_out_dBm) + "dBm")
    
        # for MAC properties
        def do_panid(self, line):
            """
            panid
    
                Get the IEEE 802.15.4 PAN ID value. Applicable on Border Router side only.
    
                > panid
                0xdead
                Done
    
            panid <panid>
    
                Set the IEEE 802.15.4 PAN ID value.
    
                > panid 0xdead
                Done
            """
            params = line.split(" ")
            if params[0] != "":
                if self.prop_get_value(SPINEL.PROP_NET_ROLE) != 0:
                    print("Error: Device role must be Border Router for PAN ID configuration.")
                    return
                router_state = self.prop_get_value(SPINEL.PROP_NET_STATE)
                if router_state != 0:
                    print("Error: PAN ID configuration must be done in network state 0 (before network start)")
                    return
    
            self.handle_property(line, SPINEL.PROP_MAC_15_4_PANID, 'H')
            self.my_panid = self.prop_get_value(SPINEL.PROP_MAC_15_4_PANID)
    
        # for NET properties
        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 Wi-SUN Network interface.
    
                > ifconfig up
                Done
    
            ifconfig down
    
                Bring down the Wi-SUN Network interface.
                Currently not implemented. Will be implemented in future.
    
                > ifconfig down
                Done
    
            ifconfig
    
                Show the status of the Wi-SUN Network interface.
    
                > ifconfig
                down
                Done
            """
    
            self.clear_routing_table()
            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_wisunstack(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_wisunstack(self, line):
            """
            wisunstack start
    
                Enable Wi-SUN stack operation and attach to a Wi-SUN network.
    
                > wisunstack start
                Done
    
            wisunstack stop
    
                Disable Wi-SUN stack operation and detach from a Wi-SUN network.
                Currently not implemented. Will be implemented in future.
                Till then use 'reset' command to stop all operations and issue
                'ifconfig up' and 'wisunstack start' to start the Wi-SUN network again.
    
                > wisunstack stop
                Done
    
            wisunstack
    
                Show the operational status of the Wi-SUN stack.
    
                > wisunstack
                stop
                Done
            """
            map_arg_value = {
                0: "stop",
                1: "start",
            }
    
            map_arg_name = {
                "stop": "0",
                "start": "1",
            }
    
            if line:
                try:
                    # remap string state names to integer
                    line = map_arg_name[line]
    
                    if "1" in line:
                        # clear routing table if wisunstack start is called
                        self.clear_routing_table()
                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])
                t = time.localtime()
                print("Done at " + time.asctime(t))
            else:
                print("Error")
    
    
        def do_role(self, line):
            """
            role
    
                Display the role of the device in the Wi-SUN network - Router, Border Router.
    
                > role
                1 : Router
                Done
            """
    
            value = self.prop_get_value(SPINEL.PROP_NET_ROLE)
            if value != None:
                map_arg_value = {
                    0: "Border-Router",
                    1: "Router",
                }
                print(str(value) + " : " + map_arg_value[value])
    
            print("Done")
    
        def do_networkname(self, line):
            """
            networkname
    
                Get the Wi-SUN Network Name.
    
                > networkname
                wisunnet
                Done
    
            networkname <name>
    
                Set the Wi-SUN Network Name. Max string length = 32 characters.
    
                > networkname wisunnet
                Done
            """
    
            self.handle_property(line, SPINEL.PROP_NET_NETWORK_NAME, 'U')
    
    
        # for TI Wi-SUN specific PHY properties
    
        def do_region(self, line):
            """
            region
                Get the Wi-SUN Network's regulatory region of operation.
                1 - NA, 2 - JP, 3 - EU, 7 - BZ, FF --> Custom region
    
                > region
                1
                Done
    
            """
            value = self.prop_get_value(SPINEL.PROP_PHY_REGION)
            if value != None:
                map_arg_value = {
                    1: "North-America",
                    2: "Japan",
                    3: "Europe",
                    7: "Brazil",
                    255: "Custom",
                }
                print(str(value) + " : " + map_arg_value[value])
    
            print("Done")
    
        def do_phymodeid(self, line):
            """
            phymodeid
                Get the modeID set for Wi-SUN network's operation.
                Supported values (1-7)
    
                > phymodeid
                2
                Done
    
            """
            value = self.prop_get_value(SPINEL.PROP_PHY_MODE_ID)
            print(value)
            print("Done")
    
        def do_unicastchlist(self, line):
            """
            unicastchlist
                Get or Set the Bit Mask to specify what channels can be used for unicast transmissions.
                Each bit in the bit mask represents if the channel is present or not
                NA region has 129 channels maximum, thus max bit mask is 17 bytes long
    
                > unicastchlist
                Channel List = 0-128
                Bit Mask = ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:01
    
                > unicastchlist 0-128
                Channel List = 0-128
                Bit Mask = ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:01
                Done
    
                > unicastchlist 0-7:15-20:33-46
                Channel List = 0-7:15-20:33-46
                Bit Mask = ff:80:1f:00:fe:7f:00:00:00:00:00:00:00:00:00:00:00
                Done
    
                > unicastchlist
                Channel List = 0-7:15-20:33-46
                Bit Mask = ff:80:1f:00:fe:7f:00:00:00:00:00:00:00:00:00:00:00
    
            """
            params = line.split(" ")
    
            if params[0] == "": # get
                value = self.prop_get_value(SPINEL.PROP_PHY_UNICAST_CHANNEL_LIST)
                arr_value = [0]*17;
                for i in range(17):
                    arr_value[i] = hex(int.from_bytes(value[i : (i+1)], "little", signed=False))
                byte_array_input_string = wisun_util.change_format_input_string(arr_value)
                chan_num_list = wisun_util.convert_to_chan_num_list(byte_array_input_string)
                print("Channel List = " + str(chan_num_list))
                print("Bit Mask = " + byte_array_input_string)
            else:
                converted_bitmask, inp_bytes = wisun_util.convert_to_bitmask(params[0])
                print("Channel List = " + str(params[0]))
                print("Bit Mask = " + wisun_util.format_display_string(str(converted_bitmask)))
                self.wpan_api.chlist_send(inp_bytes, SPINEL.PROP_PHY_UNICAST_CHANNEL_LIST)
            print("Done")
    
        def do_broadcastchlist(self, line):
            """
            broadcastchlist
                Get or Set the Bit Mask to specify what channels can be used for broadcast transmissions.
                Applicable only on the border router side.
                Bit Mask where each bit represents if the channel is present or not
                NA region has 129 channels maximum, thus max bit mask is 17 bytes long
    
                > broadcastchlist
                Channel List = 0-128
                Bit Mask = ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:01
    
                > broadcastchlist 0-128
                Channel List = 0-128
                Bit Mask = ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:01
                Done
    
                > broadcastchlist 0-7:15-20:33-46
                Channel List = 0-7:15-20:33-46
                Bit Mask = ff:80:1f:00:fe:7f:00:00:00:00:00:00:00:00:00:00:00
                Done
    
                > broadcastchlist
                Channel List = 0-7:15-20:33-46
                Bit Mask = ff:80:1f:00:fe:7f:00:00:00:00:00:00:00:00:00:00:00
    
            """
            params = line.split(" ")
    
            if params[0] == "": # get
                value = self.prop_get_value(SPINEL.PROP_PHY_BROADCAST_CHANNEL_LIST)
                arr_value = [0]*17;
                for i in range(17):
                    arr_value[i] = hex(int.from_bytes(value[i : (i+1)], "little", signed=False))
                byte_array_input_string = wisun_util.change_format_input_string(arr_value)
                chan_num_list = wisun_util.convert_to_chan_num_list(byte_array_input_string)
                print("Channel List = " + str(chan_num_list))
                print("Bit Mask = " + byte_array_input_string)
            else:
                converted_bitmask, inp_bytes = wisun_util.convert_to_bitmask(params[0])
                print("Channel List = " + str(params[0]))
                print("Bit Mask = " + wisun_util.format_display_string(str(converted_bitmask)))
                self.wpan_api.chlist_send(inp_bytes, SPINEL.PROP_PHY_BROADCAST_CHANNEL_LIST)
            print("Done")
    
        def do_asyncchlist(self, line):
            """
            asyncchlist
                Get or Set the Bit Mask to specify what channels can be used for async transmissions.
                Bit Mask where each bit represents if the channel is present or not
                NA region has 129 channels maximum, thus max bit mask is 17 bytes long
    
                > asyncchlist
                Channel List = 0-128
                Bit Mask = ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:01
    
                > asyncchlist 0-128
                Channel List = 0-128
                Bit Mask = ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:01
                Done
    
                > asyncchlist 0-7:15-20:33-46
                Channel List = 0-7:15-20:33-46
                Bit Mask = ff:80:1f:00:fe:7f:00:00:00:00:00:00:00:00:00:00:00
                Done
    
                > asyncchlist
                Channel List = 0-7:15-20:33-46
                Bit Mask = ff:80:1f:00:fe:7f:00:00:00:00:00:00:00:00:00:00:00
    
            """
            params = line.split(" ")
            if params[0] == "": # get
                value = self.prop_get_value(SPINEL.PROP_PHY_ASYNC_CHANNEL_LIST)
    
                arr_value = [0]*17;
                for i in range(17):
                    arr_value[i] = hex(int.from_bytes(value[i : (i+1)], "little", signed=False))
    
                byte_array_input_string = wisun_util.change_format_input_string(arr_value)
                chan_num_list = wisun_util.convert_to_chan_num_list(byte_array_input_string)
                print("Channel List = " + str(chan_num_list))
                print("Bit Mask = " + byte_array_input_string)
            else:
                converted_bitmask, inp_bytes = wisun_util.convert_to_bitmask(params[0])
                print("Channel List = " + str(params[0]))
                print("Bit Mask = " + wisun_util.format_display_string(str(converted_bitmask)))
                self.wpan_api.chlist_send(inp_bytes, SPINEL.PROP_PHY_ASYNC_CHANNEL_LIST)
            print("Done")
    
        def do_chspacing(self, line):
    
            """
            chspacing
                Get the channel spacing in kHz.
    
                > chspacing
                100 kHz
                Done
    
            """
            value = self.prop_get_value(SPINEL.PROP_PHY_CH_SPACING)
            ans = int.from_bytes(value, "little", signed=False)
            print(str(ans) + " kHz")
            print("Done")
    
        def do_ch0centerfreq(self, line):
    
            """
            ch0centerfreq
                Get the Channel 0 Center frequency formatted as {Ch0-MHz, Ch0-KHz}.
    
                > ch0centerfreq
                {902,200}
                Done
    
            """
            value = self.prop_get_value(SPINEL.PROP_PHY_CHO_CENTER_FREQ)
            freqMHz = int.from_bytes(value[:2], "little", signed=False)
            freqkHz = int.from_bytes(value[2:4], "little", signed=False)
            print("{" + str(freqMHz) + " MHz, " + str(freqkHz) + " kHz}")
            print("Done")
    
    
        # for TI Wi-SUN specific MAC properties
        def do_ucdwellinterval(self, line):
            """
            ucdwellinterval
                Get or Set Unicast dwell Interval (0 - 255 ms)
    
                > ucdwellinterval
                100
                Done
    
                > ucdwellinterval  100
                Done
    
            """
            self.handle_property(line, SPINEL.PROP_MAC_UC_DWELL_INTERVAL, mixed_format='B')
    
        def do_bcdwellinterval(self, line):
            """
            bcdwellinterval
                Get or Set Broadcast dwell Interval (0 - 255 ms).
                Applicable only on the border router side.
    
                > bcdwellinterval
                100
                Done
    
                > bcdwellinterval  100
                Done
    
            """
            self.handle_property(line, SPINEL.PROP_MAC_BC_DWELL_INTERVAL, mixed_format='B')
    
    
        def do_bcinterval(self, line):
            """
            bcinterval
                Get or Set Broadcast Interval (0 - 0xFFFFFF ms).
                Applicable only on the border router side.
    
                > bcinterval
                0xFFFF
                Done
    
                > bcinterval  0xFFFF
                Done
    
            """
            self.handle_property(line, SPINEL.PROP_MAC_BC_INTERVAL, mixed_format = 'L')
    
    
        def do_ucchfunction(self, line):
            """
            ucchfunction
                Get or Set Unicast Channel Function.
                0 - Fixed, 2 - Hopping based on DH1CF
    
                > ucchfunction
                1
                Done
    
                > ucchfunction  2
                Done
    
            """
            self.handle_property(line, SPINEL.PROP_MAC_UC_CHANNEL_FUNCTION, mixed_format = 'B')
    
        def do_bcchfunction(self, line):
            """
            bcchfunction
                Get or Set Broadcast Channel Function.
                0 - Fixed, 1 - Hopping based on DH1CF
                Applicable only on the border router side.
    
                > bcchfunction
                1
                Done
    
                > bcchfunction  1
                Done
    
            """
            self.handle_property(line, SPINEL.PROP_MAC_BC_CHANNEL_FUNCTION, mixed_format = 'B')
    
        def do_macfiltermode(self, line):
            """
            macfiltermode
                Get or Set the filtering mode at MAC layer.
                0 - Disabled, 1 - Whitelist, 2 - Blacklist
    
                > macfiltermode
                1
                Done
    
                > macfiltermode  1
                Done
    
            """
            self.handle_property(line, SPINEL.PROP_MAC_FILTER_MODE, mixed_format='b')
    
        # for TI Wi-SUN specific NET properties
        def do_revokeDevice(self, line):
            """
            revokeDevice
    
                Write-only property intended to remove rogue devices from network.
    
                > revokeDevice dead00beef00cafe
                Done
    
            """
            if line == None or line == '':
                print("\n Error: Please specify EUI-64 to whom access needs to be revoked")
                return
    
            self.handle_property(line, SPINEL.PROP_REVOKE_GTK_HWADDR, 'E')
    
        def do_ping(self, line):
            """
            ping <address> [size] [count] [interval] [hop limit]
    
                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"
            _hop_limit = 64
            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]
            if len(params) > 4:
                _hop_limit = int(params[4])
    
            try:
                is_mac_addr = False
                try:
                    addr_convert = ipaddress.IPv6Address(addr)
                except ipaddress.AddressValueError:
                    if (len(addr) != 16):
                        print ("Invalid IP/MAC address")
                        return
                    is_mac_addr = True
                    # covert MAC addr to LL IPv6 Address
                    # Add colons to MAC addr
                    addr = [''.join(i) for i in zip(addr[0::4], addr[1::4], addr[2::4], addr[3::4])]
                    addr = ':'.join(addr)
                    # Replace 2nd char with 2
                    addr = addr[:1] + '2' + addr[2:]
                    # Prepend fe08
                    addr = "fe80::" + addr
                    print("Converted MAC address input to {}".format(addr))
    
    
                # Generate local ping packet and send directly via spinel.
                value = self.prop_get_value(SPINEL.PROP_IPV6_ADDRESS_TABLE)
                ipv6AddrTableList = self._parse_ipv6addresstable_property(value)
                srcIPAddress = "None"
                for i in range(0,len(ipv6AddrTableList)):
                    if is_mac_addr == True:
                        if('fe80' in str(ipv6AddrTableList[i]["ipv6Addr"])):
                            srcIPAddress = str(ipv6AddrTableList[i]["ipv6Addr"])
                            break
                    else: # ip address
                        if('fe80' not in str(ipv6AddrTableList[i]["ipv6Addr"])):
                            srcIPAddress = str(ipv6AddrTableList[i]["ipv6Addr"])
                            break
    
                if srcIPAddress == "None":
                    print("Cannot perform ping request as device does not have a valid source IP address")
                    return
    
                for i in range(0,int( _count)):
                    timenow = int(round(time.time() * 1000)) & 0xFFFFFFFF
                    data = bytearray(int(_size))
    
                    ping_req = self.ipv6_factory.build_icmp_echo_request(
                        srcIPAddress,
                        addr,
                        data,
                        hop_limit=_hop_limit,
                        identifier=(timenow >> 16),
                        sequence_number=(timenow & 0xffff))
    
                    self.wpan_api.ip_send(ping_req)
                    # Let handler print result
                    time.sleep(int(_interval))
            except:
                print("Fail")
                print(traceback.format_exc())
    
        def do_eapolallowlist(self, line):
            """
            eapolallowlist <filename>
    
            Add the EUIs listed in <filename> to the eapol allowlist. Put one EUI address per line in the
            specified file.
            """
            params = line.split(" ")
            if params[0] == "":
                print("Specify EAPOL allowlist source filename")
                return
    
            filename = params[0]
    
            oldmode = None
            try:
                with open(filename, "r") as fp:
                    # Save old macfiltermode to apply later
                    oldmode = self.prop_get_value(SPINEL.PROP_MAC_FILTER_MODE)
    
                    self.prop_set_value(SPINEL.PROP_MAC_FILTER_MODE, 3, 'b')
                    lines = fp.read().splitlines()
                    for line in lines:
                        if (len(line) != 16):
                            print("Incorrect EUI length")
                            return
                        arr = util.hex_to_bytes(line)
                        self.prop_insert_value(SPINEL.PROP_MAC_MAC_FILTER_LIST, arr, str(len(arr)) + 's')
                        print("Added {} to EAPOL EUI allowlist".format(line))
                    self.prop_set_value(SPINEL.PROP_MAC_FILTER_MODE, oldmode, 'b')
            except:
                if (oldmode):
                    self.prop_set_value(SPINEL.PROP_MAC_FILTER_MODE, oldmode, 'b')
                print("Error opening or parsing EAPOL allowlist file")
                print(traceback.format_exc())
                return
    
        def do_testmetrics(self, line):
            """
            testmetrics <filename> - retrieve test metrics from Coap
                        Add the IP Addresses in the specified file.
            testmetrics            - retrieve metrics from BR
            """
            global coap_testmetrics_req_token
    
            params = line.split(" ")
            data = []
            if params[0] == "":
                test_metrics_data = []
                data = self.prop_get_or_set_value(SPINEL.PROP_PHY_METRICS, line="", mixed_format='B')
                # print("BR metrics: {}".format(list(data)))
                if len(data) > 1:
                    hwaddr = self.handle_property(line, SPINEL.PROP_HWADDR, 'E', output=False)
                    hwaddr = binascii.hexlify(hwaddr).decode('utf8')
                    test_metrics_data.append(hwaddr)
                    test_metrics_data.extend(self.parsePayLoadData(data, NODE_TYPE_BR))
                    self.saveTestMetrics (test_metrics_data, TEST_METRICS_BR_FILE_NAME)
                else:
                    print("BR metrics not available. {}".format(list(data)))
                return
    
            filename = params[0]
    
            try:
                with open(filename, "r") as fp:
                    coap_confirm = ipv6.COAP_TYPE_CON
                    uri_path = "metrics"
                    option_list = []
                    lines = fp.read().splitlines()
    
                    value = self.prop_get_value(SPINEL.PROP_IPV6_ADDRESS_TABLE)
                    ipv6AddrTableList = self._parse_ipv6addresstable_property(value)
                    srcIPAddress = "None"
                    for i in range(0,len(ipv6AddrTableList)):
                        if('fe80' not in str(ipv6AddrTableList[i]["ipv6Addr"])):
                            srcIPAddress = str(ipv6AddrTableList[i]["ipv6Addr"])
                            break
    
                    # Generate a random token and save it for ACK usage later
                    coap_testmetrics_req_token = random.getrandbits(DEFAULT_TKL*8)
    
                    for line in lines:
                        addr = line
    
                        coap_req = self.ipv6_factory.build_coap_request(srcIPAddress, addr, coap_confirm,
                            ipv6.COAP_METHOD_CODE_GET, uri_path, option_list, None, None,
                            tkl=DEFAULT_TKL, token=coap_testmetrics_req_token)
    
                        if coap_req is not None:
                            self.wpan_api.ip_send(coap_req)
                            # this delay helps when there are multiple calls
                            time.sleep(0.8)
    
            except:
                print("Error opening or parsing IP Address list file")
                print(traceback.format_exc())
                return
    
        def do_macfilterlist(self, line):
            """
            macfilterList
    
               Display the addressfilter based on the value set using macfiltermode
    
            macfilterlist add <extaddr>
    
                Add an IEEE 802.15.4 Extended Address to the address filter.
    
                > macfilterlist add dead00beef00cafe
                Done
    
            macfilterlist remove <extaddr>
    
                Remove an IEEE 802.15.4 Extended Address from the address filter.
    
                > macfilter remove dead00beef00caff
                Done
            """
            params = line.split(" ")
            if params[0] == "":
                value = self.prop_get_value(SPINEL.PROP_MAC_FILTER_MODE)
                if value == 0 or value is None:
                    print("Error: set the filter mode first: 1 for accessing WhiteList and 2 for accessing BlackList")
                    return value
    
                #get and display the content of BlackList/WhiteList
                value = self.prop_get_value(SPINEL.PROP_MAC_MAC_FILTER_LIST)
    
                size = 0x8
                # break the byte stream into different entries
                addrEntries = [value[i:i + size] for i in range(0, len(value), size)]
                #print each address entry
                for addrEntry in addrEntries:
                    print(binascii.hexlify(addrEntry).decode('utf8'))
    
            elif params[0] == "add":
                arr = util.hex_to_bytes(params[1])
                self.prop_insert_value(SPINEL.PROP_MAC_MAC_FILTER_LIST, arr, str(len(arr)) + 's')
    
            elif params[0] == "remove":
                arr = util.hex_to_bytes(params[1])
                self.prop_remove_value(SPINEL.PROP_MAC_MAC_FILTER_LIST, arr, str(len(arr)) + 's')
    
        # for TI Wi-SUN specific NET properties
        def do_routerstate(self, line):
            """
            routerstate
                Display the current join state of the Wi-SUN router device. The different states are:
                0: "Idle",
                1: "Scanning for suitable network",
                2: "Authentication in Progress",
                3: "Acquiring PAN Configuration",
                4: "Configuring Routing & DHCP based Unique IPv6 address",
                5: "Successfully joined and operational"
    
                > routerstate
                5
                Successfully joined and operational
                Done
            """
            map_arg_value = {
                0: "Idle",
                1: "Scanning for suitable network",
                2: "Authentication in Progress",
                3: "Acquiring PAN Configuration",
                4: "Configuring Routing & DHCP based Unique IPv6 address",
                5: "Successfully joined and operational"
            }
    
            result = self.prop_get_value(SPINEL.PROP_NET_STATE)
            print(result)
            if result != None:
                state = map_arg_value[result]
                print(state)
                print("Done")
            else:
                print("Error")
    
        def do_multicastlist(self, line):
            """
            multicastlist
    
               Display the multicast groups this device is subscribed to. Note that this command only displays
               multicast groups above realm scope (scop 3). The device is already subscribed to existing well-known
               interface, link, and realm-local multicast groups as specified by the Wi-SUN standard.
    
            multicastlist add <ipv6addr>
    
                Add an IPv6 address to the MPL domain and multicast group list for this device. Note that this
                command can only add groups above realm scope (scop 3).
    
                > multicastlist add ff05::3
                Done
    
            multicastlist remove <ipv6addr>
    
                Remove an IPv6 from the MPL domain and multicast group list for this device. Note that this
                command can only remove groups above realm scope (scop 3).
    
                > multicastlist remove ff05::3
                Done
            """
            router_state = self.prop_get_value(SPINEL.PROP_NET_STATE)
            if router_state < 5:
                print("Error: Device must be in join state 5 (Successfully joined and operational) to process multicast commands")
                return
    
            params = line.split(" ")
            if params[0] == "":
                value = self.prop_get_value(SPINEL.PROP_MULTICAST_LIST)
                # Break the byte stream into different entries (skipping the first 2 length bytes)
                addrEntries = [value[i:i + IPV6_ADDR_LEN] for i in range(2, len(value), IPV6_ADDR_LEN)]
                for addrEntry in addrEntries:
                    print(ipaddress.IPv6Address(addrEntry))
    
            elif params[0] == "add" or params[0] == "remove":
                try:
                    ipaddr = ipaddress.IPv6Address(params[1])
                except:
                    print("Error: Invalid IPv6 address")
                    return
                if not ipaddr.is_multicast:
                    print("Error: IPv6 address is not multicast")
                    return
    
                if params[0] == "add":
                    self.prop_insert_value(SPINEL.PROP_MULTICAST_LIST, ipaddr.packed, str(len(ipaddr.packed)) + 's')
                elif params[0] == "remove":
                    self.prop_remove_value(SPINEL.PROP_MULTICAST_LIST, ipaddr.packed, str(len(ipaddr.packed)) + 's')
    
        def do_udp(self, line):
            """
            udp <address> [payload] [count] [interval] [hop limit]
                Send a UDP message to a target address.
    
                > udp fd00:7283:7e00:0:212:4b00:1ca1:9463 testdata
                Sending UDP packet with payload: testdata
    
            Embedded UDP test mode:
            udp start <#of Packets> <packet interval> <hop count> <packet length> - to start sending #of UDP packets from Border Router.
                # of Packets - # of packets to send. Set #of Packets to 0 to send packets indefintely
                packet interval - duration between each packet (default 1sec)
                hop count - hop count set on each packet (default 1)
                packet length - length of each packet (default 20, maximum is 250)
    
                Examples:
                    > udp start 100
                    > udp start 100 2 3 40
            """
            router_state = self.prop_get_value(SPINEL.PROP_NET_STATE)
            if router_state < 5:
                print("Error: Device must be in join state 5 (Successfully joined and operational) to process udp commands")
                return
    
            params = line.split(" ")
            if len(params) < 1:
                print("Invalid number of parameters")
                return
    
            if len(params) > 1:
                if (params[0] == "start"):
                    pkts = params[1]
                    print ("Send UDP pkts for: " + str(pkts))
                    # Default values, when they are not specified
                    interval = 1
                    pktHopCount = 1
                    pktLen = 20
                    if len(params) > 4:
                        interval = params[2]
                        pktHopCount = params[3]
                        pktLen = params[4]
    
                    # Encode parameters in payload to send
                    payload = (int(pkts) & 0xFFFFFFFF) + ((int(interval) & 0xFF) << 32) + ((int(pktHopCount) & 0xFF) << 40)
                    if (int(pktLen) < 0x14 or int(pktLen) > 0xFF):
                        pktLen = 0x14
                    payload = payload + ((int(pktLen) & 0xFF) << 48)
                    payload_str = "00" + f'{payload:0>14x}'
                    value = self.handle_property(payload_str, SPINEL.PROP_NET_UDP_START, 'D')
                    return
    
            try:
                addr = params[0]
                payload_data = ""
                count = 1
                interval = 1
                hop_limit = 64
                if len(params) >= 2:
                    payload_data = params[1]
                if len(params) >= 3:
                    count = int(params[2])
                if len(params) >= 4:
                    interval = int(params[3])
                if len(params) >= 5:
                    hop_limit = int(params[4])
    
                # Decide if address is a MAC or IP address
                is_mac_addr = False
                try:
                    addr_convert = ipaddress.IPv6Address(addr)
                except ipaddress.AddressValueError:
                    if (len(addr) != 16):
                        print ("Invalid IP/MAC address")
                        return
                    is_mac_addr = True
                    # covert MAC addr to LL IPv6 Address
                    # Add colons to MAC addr
                    addr = [''.join(i) for i in zip(addr[0::4], addr[1::4], addr[2::4], addr[3::4])]
                    addr = ':'.join(addr)
                    # Replace 2nd char with 2
                    addr = addr[:1] + '2' + addr[2:]
                    # Prepend fe08
                    addr = "fe80::" + addr
                    print("Converted MAC address input to {}".format(addr))
    
                value = self.prop_get_value(SPINEL.PROP_IPV6_ADDRESS_TABLE)
                ipv6AddrTableList = self._parse_ipv6addresstable_property(value)
                srcIPAddress = "None"
                for i in range(0,len(ipv6AddrTableList)):
                    if is_mac_addr == True:
                        if('fe80' in str(ipv6AddrTableList[i]["ipv6Addr"])):
                            srcIPAddress = str(ipv6AddrTableList[i]["ipv6Addr"])
                            break
                    else: # IP address
                        if('fe80' not in str(ipv6AddrTableList[i]["ipv6Addr"])):
                            srcIPAddress = str(ipv6AddrTableList[i]["ipv6Addr"])
                            break
    
                if srcIPAddress == "None":
                    print("Cannot perform CoAP request as device does not have a valid source IP address")
                    return
    
                for i in range(0,count):
                    print("Sending UDP packet with payload: {}".format(payload_data))
                    udp_req = self.ipv6_factory.build_udp_request(srcIPAddress, addr, payload=payload_data.encode('utf-8'), hop_limit=hop_limit)
                    if udp_req is not None:
                        self.wpan_api.ip_send(udp_req)
                        # Let handler print result
                    time.sleep(interval)
            except:
                print("Fail")
                print(traceback.format_exc())
    
        def do_coap(self, line):
            """
            coap <address> <coap request code (get|put|post)> <coap request type (con|non)> <uri_path>
                 [--led_state <led_target (r|g)> <led_state (0|1)>]
                 [--test_option <option_number> [<option_payload>]]
    
                Send a coap request. The generated coap request is designed to target the ns_coap_node project,
                allowing the NCP device to get/set the state of LaunchPad LEDs via the target's "led" CoAP resource.
    
                Parameters:
                    address:           Destination address for coap request. Can be link local MAC or IP address.
                                       Multicast addresses are not supported.
                    coap request code: Specify get, put, or post as the CoAP request code
                    coap request type: Specify con (confirmable) or non (non-confirmable) as the CoAP request type.
                    uri_path:          Specify the path of the URI resource. Specify led to target the ns_coap_node
                                       LED resource.
                    --led_state:       Specify --led_state followed by the target LED (r or RLED or g for GLED) and
                                       state to set the LED (0 for off, 1 for on). Only valid for put or post requests.
                    --test_option:     Optional argument to add an additional option to the request. Specify
                                       --test_option followed by an option number and option payload. See RFC7252 for
                                       details on CoAP options.
    
                Examples:
                    Get request with MAC address (confirmable):
                        > coap 00124b0014f942aa get con led
    
                    Get request (confirmable):
                        > coap fdde:ad00:beef:0:558:f56b:d688:799 get con led
    
                    Get request (nonconfirmable):
                        > coap fdde:ad00:beef:0:558:f56b:d688:799 get non led
    
                    Post request (set RLED to on state)
                        > coap fdde:ad00:beef:0:558:f56b:d688:799 post con led --led_state r 1
    
                    Get request with test option:
                        > coap fdde:ad00:beef:0:558:f56b:d688:799 get con led --test_option 3 hostname
            """
            global coap_led_req_token
            global coap_testmetrics_req_token
    
            router_state = self.prop_get_value(SPINEL.PROP_NET_STATE)
            if router_state < 5:
                print("Error: Device must be in join state 5 (Successfully joined and operational) to process coap commands")
                return
    
            params = line.split(" ")
            if len(params) < 4:
                print("Invalid number of parameters")
                return
    
            try:
                coap_req = None
                coap_confirm = None
                addr = params[0]
                if params[2] == 'con':
                    coap_confirm = ipv6.COAP_TYPE_CON
                elif params[2] == 'non':
                    coap_confirm = ipv6.COAP_TYPE_NON
                else:
                    print("Invalid CoAP request type. Must be con or non")
                    return
                uri_path = params[3]
    
                # Decide if address is a MAC or IP address
                is_mac_addr = False
                try:
                    addr_convert = ipaddress.IPv6Address(addr)
                except ipaddress.AddressValueError:
                    if (len(addr) != 16):
                        print ("Invalid IP/MAC address")
                        return
                    is_mac_addr = True
                    # covert MAC addr to LL IPv6 Address
                    # Add colons to MAC addr
                    addr = [''.join(i) for i in zip(addr[0::4], addr[1::4], addr[2::4], addr[3::4])]
                    addr = ':'.join(addr)
                    # Replace 2nd char with 2
                    addr = addr[:1] + '2' + addr[2:]
                    # Prepend fe08
                    addr = "fe80::" + addr
                    print("Converted MAC address input to {}".format(addr))
    
                value = self.prop_get_value(SPINEL.PROP_IPV6_ADDRESS_TABLE)
                ipv6AddrTableList = self._parse_ipv6addresstable_property(value)
                srcIPAddress = "None"
                for i in range(0,len(ipv6AddrTableList)):
                    if is_mac_addr == True:
                        if('fe80' in str(ipv6AddrTableList[i]["ipv6Addr"])):
                            srcIPAddress = str(ipv6AddrTableList[i]["ipv6Addr"])
                            break
                    else: # IP address
                        if('fe80' not in str(ipv6AddrTableList[i]["ipv6Addr"])):
                            srcIPAddress = str(ipv6AddrTableList[i]["ipv6Addr"])
                            break
    
                if srcIPAddress == "None":
                    print("Cannot perform CoAP request as device does not have a valid source IP address")
                    return
    
                option_list = []
                led_target = None
                led_state = None
                if len(params) > 4:
                    for i in range(4, len(params)):
                        if params[i] == '--test_option' and len(params) >= (i + 1):
                            option_payload = None
                            if len(params) >= (i+2):
                                option_payload = params[i+2].encode('utf-8')
                            option_list.append(ipv6.CoAPOption(int(params[i+1]), option_payload))
                        if params[i] == '--led_state' and len(params) >= (i + 2):
                            if params[i+1] == 'r':
                                led_target = COAP_RLED_ID
                            elif params[i+1] == 'g':
                                led_target = COAP_GLED_ID
                            else:
                                print("Invalid LED target, must be g or r")
                                return
    
                            if params[i+2] == '0':
                                led_state = 0
                            elif params[i+2] == '1':
                                led_state = 1
                            else:
                                print("Invalid LED state, must be 0 or 1")
                                return
    
                # Generate a random token and save it for ACK usage later
                coap_led_req_token = random.getrandbits(DEFAULT_TKL*8)
                coap_testmetrics_req_token = random.getrandbits(DEFAULT_TKL*8)
    
                if uri_path == "led":
                    if params[1] == "get":
                        coap_req = self.ipv6_factory.build_coap_request(srcIPAddress, addr, coap_confirm,
                            ipv6.COAP_METHOD_CODE_GET, uri_path, option_list, led_target, led_state,
                            tkl=DEFAULT_TKL, token=coap_led_req_token)
                    elif params[1] == "put" or params[1] == "post":
                        if params[1] == "put":
                            coap_req = self.ipv6_factory.build_coap_request(srcIPAddress, addr, coap_confirm,
                                ipv6.COAP_METHOD_CODE_PUT, uri_path, option_list, led_target, led_state,
                                tkl=DEFAULT_TKL, token=coap_led_req_token)
                        else:
                            coap_req = self.ipv6_factory.build_coap_request(srcIPAddress, addr, coap_confirm,
                                ipv6.COAP_METHOD_CODE_POST, uri_path, option_list, led_target, led_state,
                                tkl=DEFAULT_TKL, token=coap_led_req_token)
                    else:
                        print("Invalid CoAP request code for led")
                        return
                elif uri_path == "metrics":
                    if params[1] == "get":
                        coap_req = self.ipv6_factory.build_coap_request(srcIPAddress, addr, coap_confirm,
                            ipv6.COAP_METHOD_CODE_GET, uri_path, option_list, None, None,
                            tkl=DEFAULT_TKL, token=coap_testmetrics_req_token)
                    else:
                        print("Invalid CoAP request code for metrics")
                        return
                else:
                    print("Invalid CoAP request code")
                    return
    
                if coap_req is not None:
                    self.wpan_api.ip_send(coap_req)
                    # Let handler print result
            except:
                print("Fail")
                print(traceback.format_exc())
    
        def do_getpanidlist(self, line):
            """
            getpanidlist <ipv6 address> <list_type (allow|deny)>
                Retrieve the contents of the PAN ID allowlist or denylist.
                You MUST build coap node projects with the `COAP_PANID_LIST` predefine to use this functionality.
    
                Parameters:
                    ipv6 address:   Destination IPv6 address of the device. Multicast addresses are not supported.
                    list_type:      Type of PAN ID list to retrieve. Specify "allow" or "deny" for allowlist and denylist,
                                    respectively.
                Examples:
                    > getpanidlist 2020:abcd::212:4b00:14f7:d2ee allow
                    Sending PAN ID allow/deny list get message
                    CoAP packet received from 2020:abcd::212:4b00:14f7:d2ee: ...
                    PAN ID list contents:
                    0xabcd
                    0x2345
    
                    > getpanidlist 2020:abcd::212:4b00:14f7:d2ee deny
                    Sending PAN ID allow/deny list get message
                    CoAP packet received from 2020:abcd::212:4b00:14f7:d2ee: ...
                    PAN ID list is empty!
            """
            global panid_list_get_token
    
            # Verify and assign paramters
            params = line.split(" ")
            if len(params) != 2:
                print("Invalid number of parameters")
                return
            dest_addr = params[0]
            list_type = params[1]
            list_type_uri= None
    
            if list_type == 'allow':
                list_type_uri = PANID_LIST_ALLOW_URI
            elif list_type == 'deny':
                list_type_uri = PANID_LIST_DENY_URI
            else:
                print('Invalid PAN ID list type. Specify allow or deny')
                return
    
            # Check router state
            router_state = self.prop_get_value(SPINEL.PROP_NET_STATE)
            if router_state < 5:
                print("Error: Device must be in join state 5 (Successfully joined and operational) to process coap commands")
                return
            try:
                # Get source address
                value = self.prop_get_value(SPINEL.PROP_IPV6_ADDRESS_TABLE)
                ipv6AddrTableList = self._parse_ipv6addresstable_property(value)
                src_addr = None
                for i in range(0,len(ipv6AddrTableList)):
                    if('0xFE80' not in str(ipv6AddrTableList[i]["ipv6Addr"])):
                        src_addr = str(ipv6AddrTableList[i]["ipv6Addr"])
                        break
                if src_addr is None:
                    print("Cannot perform CoAP request as device does not have a valid source IP address")
                    return
    
                # Generate a random token and save it for ACK usage later
                panid_list_get_token = random.getrandbits(DEFAULT_TKL*8)
    
                # Construct coap message
                coap_req = self.ipv6_factory.build_coap_request(src_addr, dest_addr, ipv6.COAP_TYPE_CON,
                    ipv6.COAP_METHOD_CODE_GET, PANID_LIST_BASE_URI + '/' + list_type_uri, tkl=DEFAULT_TKL,
                    token=panid_list_get_token)
    
                # Send coap message
                if coap_req is not None:
                    self.wpan_api.ip_send(coap_req)
                    print("Sending PAN ID allow/deny list get message")
                    # Let handler print result
                else:
                    print("CoAP message not generated successfully")
            except:
                print("Failed to send request")
                print(traceback.format_exc())
    
        def do_setpanidlistjson(self, line):
            """
            setpanidlistjson <json_file_path>
                Set the JSON file used to set the panid allow/deny list for new coap nodes joining the network.
                You MUST build coap node projects with the `COAP_PANID_LIST` predefine to use this functionality.
                If this file is not set or does not exist, you can still use setpanidlist and panrediscover manually.
                See panid_list_example.json for an example JSON file. Note that entries IDs are EUI addresses, which
                can be retrieved from devices via Uniflash. EUI addresses MUST be capitalized in the file. See the
                README for more details on setting this file.
    
                If this file is set and does exist, send the contents of the entry corresponding to the joining
                coap node based on this file. The joining node will automatically add this content to its current
                panid filter list.  If a rediscover is necessary (if the joined coap node is not allowed to join
                the currently connected PAN), then a pan rediscover is triggered automatically.
    
                Parameters:
                    json_file_path: Path of the json file used to set panid allow/deny list. See panid_list_example.json
                                    for an example file.
                Example:
                    > setpanidlistjson panid_list_example.json
                    PAN ID list JSON file successfully set
    
                    *** On coap node join (no rediscover case) ***
                    CoAP packet received from 2020:abcd::212:4b00:14f7:d2ee: type: 1 (Non-confirmable), token: 444124896, code: 0.02 (Post), msg_id: 11247
                    CoAP node with address 2020:abcd::212:4b00:14f7:d2ee joined!
                    Setting coap node PAN ID list according to JSON file
                    CoAP node EUI found in JSON, sending PAN ID list
    
                    CoAP packet received from 2020:abcd::212:4b00:14f7:d2ee: type: 2 (Acknowledgement), token: 17288129104006421587, code: 2.03 (Valid), msg_id: 0
                    JSON file PAN IDs added to PAN ID list.
                    PAN rediscovery not required, staying in network
    
                    *** On coap node join (rediscover case) ***
                    CoAP packet received from 2020:abcd::212:4b00:14f7:d2ee: type: 1 (Non-confirmable), token: 1044362384, code: 0.02 (Post), msg_id: 25514
                    CoAP node with address 2020:abcd::212:4b00:14f7:d2ee joined!
                    Setting coap node PAN ID list according to JSON file
                    CoAP node EUI found in JSON, sending PAN ID list
    
                    CoAP packet received from 2020:abcd::212:4b00:14f7:d2ee: type: 2 (Acknowledgement), token: 12059002529985627774, code: 2.04 (Changed), msg_id: 0
                    JSON file PAN IDs added to PAN ID list.
                    PAN rediscovery started
            """
            # Verify and assign paramters
            params = line.split(" ")
            if len(params) != 1:
                print("Invalid number of parameters")
                return
            file_path = params[0]
            if not os.path.isfile(file_path):
                print("Error: Could not read  PAN ID list JSON file")
                return
            self.panid_list_json_path = file_path
            print("PAN ID list JSON file successfuly set")
    
        def do_setpanidlist(self, line):
            """
            setpanidlist <ipv6 address> <list_type (allow|deny)> <action (add|remove)> <panid>
                Add or remove a PAN ID in the allowlist or denylist.
                You MUST build coap node projects with the `COAP_PANID_LIST` predefine to use this functionality.
    
                Parameters:
                    ipv6 address:   Destination IPv6 address of the device. Multicast addresses are not supported.
                    list_type:      Type of PAN ID list to set. Specify "allow" or "deny" for allowlist and denylist,
                                    respectively.
                    action:         Specify either "add" or "remove" to add or remove an entry for the specified list.
                    panid:          PAN ID to add or remove from the specified list.
                Examples:
                    > setpanidlist 2020:abcd::212:4b00:14f7:d2ee allow add 0xabcd
                    Sending PAN ID allow/deny list set message
                    CoAP packet received from 2020:abcd::212:4b00:14f7:d2ee ...
                    PAN ID list successfully set
    
                    > setpanidlist 2020:abcd::212:4b00:14f7:d2ee allow remove 0xabcd
                    Sending PAN ID allow/deny list set message
                    CoAP packet received from 2020:abcd::212:4b00:14f7:d2ee ...
                    PAN ID list successfully set
    
                    > setpanidlist 2020:abcd::212:4b00:14f7:d2ee deny add 0x1234
                    Sending PAN ID allow/deny list set message
                    CoAP packet received from 2020:abcd::212:4b00:14f7:d2ee ...
                    PAN ID list successfully set
    
                    > setpanidlist 2020:abcd::212:4b00:14f7:d2ee deny remove 0x1234
                    Sending PAN ID allow/deny list set message
                    CoAP packet received from 2020:abcd::212:4b00:14f7:d2ee ...
                    PAN ID list successfully set
            """
            global panid_list_set_token
    
            # Verify and assign paramters
            params = line.split(" ")
            if len(params) != 4:
                print("Invalid number of parameters")
                return
            dest_addr = params[0]
            list_type = params[1]
            action    = params[2]
            panid     = params[3]
            list_type_uri= None
            action_bit = None
    
            if list_type == 'allow':
                list_type_uri = PANID_LIST_ALLOW_URI
            elif list_type == 'deny':
                list_type_uri = PANID_LIST_DENY_URI
            else:
                print('Invalid PAN ID list type. Specify allow or deny')
                return
    
            if action == 'add':
                action_bit = PANID_LIST_ACTION_ADD
            elif action == 'remove':
                action_bit = PANID_LIST_ACTION_DEL
            else:
                print('Invalid action. Specify add or remove')
                return
            try:
                panid = int(panid, 16)
            except(ValueError):
                print("PAN ID value is not hex format")
                return
    
            # Check router state
            router_state = self.prop_get_value(SPINEL.PROP_NET_STATE)
            if router_state < 5:
                print("Error: Device must be in join state 5 (Successfully joined and operational) to process coap commands")
                return
            try:
                # Get source address
                value = self.prop_get_value(SPINEL.PROP_IPV6_ADDRESS_TABLE)
                ipv6AddrTableList = self._parse_ipv6addresstable_property(value)
                src_addr = None
                for i in range(0,len(ipv6AddrTableList)):
                    if('0xFE80' not in str(ipv6AddrTableList[i]["ipv6Addr"])):
                        src_addr = str(ipv6AddrTableList[i]["ipv6Addr"])
                        break
                if src_addr is None:
                    print("Cannot perform CoAP request as device does not have a valid source IP address")
                    return
    
                # Construct payload
                panid_lower = panid & 0xFF
                panid_upper = panid >> 8
                payload = [action_bit, panid_lower, panid_upper]
    
                # Generate a random token and save it for ACK usage later
                panid_list_set_token = random.getrandbits(DEFAULT_TKL*8)
    
                # Construct coap message
                coap_req = self.ipv6_factory.build_coap_request(src_addr, dest_addr, ipv6.COAP_TYPE_CON,
                    ipv6.COAP_METHOD_CODE_PUT, PANID_LIST_BASE_URI + '/' + list_type_uri, payload=payload, tkl=DEFAULT_TKL,
                    token=panid_list_set_token)
    
                # Send coap message
                if coap_req is not None:
                    self.wpan_api.ip_send(coap_req)
                    print("Sending PAN ID allow/deny list set message")
                    # Let handler print result
                else:
                    print("CoAP message not generated successfully")
            except:
                print("Failed to send request")
                print(traceback.format_exc())
    
        def do_panrediscover(self, line):
            """
            panrediscover <ipv6 address>
                Trigger a PAN rediscover on the specified device by reseting the network stack.
                You MUST build coap node projects with the `COAP_PANID_LIST` predefine to use this functionality.
    
                Parameters:
                    ipv6 address:   Destination IPv6 address of the device. Multicast addresses are not supported.
    
                Example:
                    > panrediscover 2020:abcd::212:4b00:14f7:d2ee
                    Sending PAN rediscover request message
                    CoAP packet received from 2020:abcd::212:4b00:14f7:d2ee ...
                    PAN rediscover successfully triggered
            """
            global pan_rediscover_req_token
    
            # Verify and assign paramters
            params = line.split(" ")
            if len(params) != 1:
                print("Invalid number of parameters")
                return
            dest_addr = params[0]
    
            # Check router state
            router_state = self.prop_get_value(SPINEL.PROP_NET_STATE)
            if router_state < 5:
                print("Error: Device must be in join state 5 (Successfully joined and operational) to process coap commands")
                return
            try:
                # Get source address
                value = self.prop_get_value(SPINEL.PROP_IPV6_ADDRESS_TABLE)
                ipv6AddrTableList = self._parse_ipv6addresstable_property(value)
                src_addr = None
                for i in range(0,len(ipv6AddrTableList)):
                    if('0xFE80' not in str(ipv6AddrTableList[i]["ipv6Addr"])):
                        src_addr = str(ipv6AddrTableList[i]["ipv6Addr"])
                        break
                if src_addr is None:
                    print("Cannot perform CoAP request as device does not have a valid source IP address")
                    return
    
                # Generate a random token and save it for ACK usage later
                pan_rediscover_req_token = random.getrandbits(DEFAULT_TKL*8)
    
                # Construct coap message
                coap_req = self.ipv6_factory.build_coap_request(src_addr, dest_addr, ipv6.COAP_TYPE_CON,
                    ipv6.COAP_METHOD_CODE_PUT, PANID_LIST_BASE_URI + '/' + PAN_REDISCOVER_URI, tkl=DEFAULT_TKL,
                    token=pan_rediscover_req_token)
    
                # Send coap message
                if coap_req is not None:
                    self.wpan_api.ip_send(coap_req)
                    print("Sending PAN rediscover request message")
                    # Let handler print result
                else:
                    print("CoAP message not generated successfully")
            except:
                print("Failed to send request")
                print(traceback.format_exc())
    
        def do_getoadfwver(self, line):
            """
            getoadfwver <ipv6 address>
                Get the firmware version of the image on a CoAP OAD-enabled device.
    
                Parameters:
                    ipv6 address:   Destination IPv6 address of the device. Multicast addresses are not supported.
    
                Example:
                    > getoadfwver fdde:ad00:beef:0:558:f56b:d688:799
                    Img ID: 123, Platform: 23 (CC1312R7)
                    OAD firmware version: 1.0.0.0
            """
            global oad_fwv_req_token
    
            # Verify and assign paramters
            params = line.split(" ")
            if len(params) != 1:
                print("Invalid number of parameters")
                return
            dest_addr = params[0]
    
            # Check router state
            router_state = self.prop_get_value(SPINEL.PROP_NET_STATE)
            if router_state < 5:
                print("Error: Device must be in join state 5 (Successfully joined and operational) to process coap commands")
                return
            try:
                # Get source address
                value = self.prop_get_value(SPINEL.PROP_IPV6_ADDRESS_TABLE)
                ipv6AddrTableList = self._parse_ipv6addresstable_property(value)
                src_addr = None
                for i in range(0,len(ipv6AddrTableList)):
                    if('0xFE80' not in str(ipv6AddrTableList[i]["ipv6Addr"])):
                        src_addr = str(ipv6AddrTableList[i]["ipv6Addr"])
                        break
                if src_addr is None:
                    print("Cannot perform CoAP request as device does not have a valid source IP address")
                    return
    
                # Generate a random token and save it for ACK usage later
                oad_fwv_req_token = random.getrandbits(DEFAULT_TKL*8)
    
                # Construct coap message
                coap_req = self.ipv6_factory.build_coap_request(src_addr, dest_addr, ipv6.COAP_TYPE_CON,
                    ipv6.COAP_METHOD_CODE_GET, OAD_REQ_URI_BASE + '/' + OAD_FWV_REQ_URI, tkl=DEFAULT_TKL,
                    token=oad_fwv_req_token)
    
                # Send coap message
                if coap_req is not None:
                    self.wpan_api.ip_send(coap_req)
                    print("Sending OAD firmware version request message")
                    # Let handler print result
                else:
                    print("CoAP message not generated successfully")
            except:
                print("Failed to send firmware version request")
                print(traceback.format_exc())
    
        def do_startoad(self, line):
            """
            startoad <ipv6 address> <platform type> <block size> <oad image path>
                Start a CoAP OAD with a target OAD device.
    
                Parameters:
                    ipv6 address:    Destination IPv6 address of target OAD-enabled device to upgrade the firmware of.
                                     Multicast addresses are not supported.
                    platform type:   Platform of the target device to be upgraded. Supported platforms are CC1312R7, CC1352P7,
                                     CC1314R10, and CC1354P10.
                    block size:      Block size (bytes) to use for block transfer. Recommended to use between 128-1024 byte blocks.
                                     Very small block size incurs unnecessary frame overhead, while very large block size results
                                     in fragmentation overhead.
                    oad image path:  Relative file path of the oad image binary file to upgrade the target OAD device
                                     with.
                Example:
                    > startoad fdde:ad00:beef:0:558:f56b:d688:799 CC1312R7 128 ns_coap_oad_offchip_LP_CC1312R7_tirtos7_ticlang.bin
                    Sending OAD notification request message
                    OAD notification response received
                    OAD upgrade accepted. Starting block transfer
            """
            global oad_file
            global oad_img_len
            global oad_ntf_req_token
            global oad_start_time
            global oad_log_filename
            global oad_block_size
    
            # Verify and assign paramters
            params = line.split(" ")
            if len(params) != 4:
                print("Invalid number of parameters")
                return
            dest_addr = params[0]
            platform_type = params[1]
            oad_block_size = int(params[2])
            img_path = params[3]
            if platform_type in PLATFORM_CHIP_TYPE_LOOKUP:
                platform_type = PLATFORM_CHIP_TYPE_LOOKUP[platform_type]
            else:
                print("Error: Unsupported platform. Supported platforms are CC1312R7, CC1352P7, CC1314R10, and CC1354P10.")
                return False
    
            self.cleanup_oad()
            oad_file = None
            # Open OAD img file
            try:
                oad_file = open(img_path, "rb")
                oad_file.seek(0, os.SEEK_END)
                oad_img_len = oad_file.tell()
                oad_file.seek(0, os.SEEK_SET)
                print("OAD image filesize: ", oad_img_len, "bytes")
            except:
                print("Error reading oad image file")
                self.cleanup_oad()
                return False
    
            # Open OAD logging file
            timestamp = time.strftime("%Y-%m-%d-T%H-%M-%S")
            dest_addr_str = dest_addr.replace(':', '-')
            oad_log_filename = "oad_log_{}_{}.txt".format(dest_addr_str, timestamp)
            oad_logger = None
            oad_log_file = None
            try:
                oad_logger = logging.getLogger('oad')
                oad_logger.setLevel(logging.INFO)
                oad_logger.progagate = False
                oad_log_file = logging.FileHandler(oad_log_filename)
                oad_log_filename = oad_log_file.baseFilename # Convert filename to full base filename
                print(oad_log_filename)
                oad_logger.addHandler(oad_log_file)
            except:
                print("Error opening oad log file")
                self.cleanup_oad()
                return False
    
            # Check router state
            router_state = self.prop_get_value(SPINEL.PROP_NET_STATE)
            if router_state < 5:
                print("Error: Device must be in join state 5 (Successfully joined and operational) to process coap commands")
                return
    
            try:
                # Get source address
                value = self.prop_get_value(SPINEL.PROP_IPV6_ADDRESS_TABLE)
                ipv6AddrTableList = self._parse_ipv6addresstable_property(value)
                src_addr = None
                for i in range(0,len(ipv6AddrTableList)):
                    if('0xFE80' not in str(ipv6AddrTableList[i]["ipv6Addr"])):
                        src_addr = str(ipv6AddrTableList[i]["ipv6Addr"])
                        break
                if src_addr is None:
                    print("Cannot perform CoAP OAD as device does not have a valid source IP address")
                    return
    
                # Read oad image from file
                mcuboot_version = []
                try:
                    oad_file.seek(MCUBOOT_VERSION_BYTE, os.SEEK_SET)
                    for _ in range(0, 8):
                        byte = oad_file.read(1)
                        mcuboot_version.append(int.from_bytes(byte, 'big'))
                except:
                    print("Error reading mcuboot version from oad image")
                    return False
    
                # Build ntf req payload
                img_len_bytes = list(oad_img_len.to_bytes(4, "little"))
                oad_block_size_lower = oad_block_size & 0xFF
                oad_block_size_upper = oad_block_size >> 8
                payload_notif_req = [
                    OAD_IMG_ID,                # img_id
                    platform_type,             # platform/chip type
                    oad_block_size_lower,      # block_size
                    oad_block_size_upper,
                ] + img_len_bytes + mcuboot_version
    
                # Generate a random token and save it for ACK usage later
                oad_ntf_req_token = random.getrandbits(DEFAULT_TKL*8)
    
                # Construct coap message
                coap_req = self.ipv6_factory.build_coap_request(src_addr, dest_addr, ipv6.COAP_TYPE_CON,
                    ipv6.COAP_METHOD_CODE_PUT, OAD_REQ_URI_BASE + '/' + OAD_NOTIF_REQ_URI, payload=payload_notif_req,
                    tkl=DEFAULT_TKL, token=oad_ntf_req_token)
    
                # Send coap message
                if coap_req is not None:
                    self.wpan_api.ip_send(coap_req)
                    print("Sending OAD notification request message")
                    print("Logging results in {}".format(oad_log_filename))
                    # Let handler print result
                else:
                    print("CoAP message not generated successfully")
            except:
                print("Failed to start OAD")
                print(traceback.format_exc())
    
        def do_getoadstatus(self, line):
            """
            getoadstatus
                Check the status of the ongoing OAD.
    
                Example:
                    > getoadstatus
                      Block 0154/2752 sent. Block size: 128. Duration: 0:00:25.804326
            """
            global oad_log_str
            if oad_log_str is not None:
                print(oad_log_str)
            else:
                print("No OAD in progress")
    
        #Helper util function to parse received PROP_IPV6_ADDRESS_TABLE property info
        def _parse_ipv6addresstable_property(self, propIPv6AddrTabInfo):
            """
            Internal utility function to convert IPv6 Addr Info into structure
            Returns a list of dictionary of IPv6 Address Table Entry
            Each Disctionary entry has ipv6Addr, prefixLen, validLifeTime and prefferedLifeTime
            """
            ipv6AddrTableList = []
    
            try:
                # 2 bytes = length of structure; 16 bytes IPv6 address; 1 byte = prefix len ; 4 bytes = valid lifetime; 4 bytes = preferred lifetime
                size = 0x1B
    
                # break the byte stream into different structure record
                addrStructs = [propIPv6AddrTabInfo[i:i + size] for i in range(0, len(propIPv6AddrTabInfo), size)]
    
                # parse each structure record as ipaddress; prefix_len; valid_lifetime; preferred_lifetime
    
                for addrStruct in addrStructs:
                    ipv6AddrTableEntry = {}
                    addr = addrStruct[2:18] # 6
                    ipv6AddrTableEntry["ipv6Addr"] = ipaddress.IPv6Address(addr)
                    ipv6AddrTableEntry["prefixLen"] = int.from_bytes(addrStruct[18:19], "little", signed=False) # C
                    ipv6AddrTableEntry["validLifeTime"] = int.from_bytes(addrStruct[19:23], "little", signed=False) # L
                    ipv6AddrTableEntry["prefferedLifeTime"] = int.from_bytes(addrStruct[23:27], "little", signed=False) # L
                    ipv6AddrTableList.append(ipv6AddrTableEntry)
            except Exception as es:
                print("Exception raised during Parsing IPv6Address Table")
                print(es)
                return([])
    
            return(ipv6AddrTableList)
    
        # for IPV6 properties
        def do_numconnected(self, line):
            """
            Displays the number of Wi-SUN FAN nodes which have joined to the Wi-SUN FAN border router device.
    
            > numconnected
            2
            Done
            """
            try:
                # Only valid for BR
                if self.prop_get_value(SPINEL.PROP_NET_ROLE) != 0:
                    print("Error: Device role must be Border Router to process this command")
                    return
                router_state = self.prop_get_value(SPINEL.PROP_NET_STATE)
                if router_state < 5:
                    print("Error: Device must be in join state 5 (Successfully joined and operational) to process this command")
                    return
    
                value = self.prop_get_value(SPINEL.PROP_NUM_CONNECTED_DEVICES)
                if value is None:
                    print("Error: Could not retrieve connected devices from embedded device")
                    return
                print(value)
                print("Done")
            except:
                print("Fail")
                print(traceback.format_exc())
    
        def do_connecteddevices(self, line):
            """
            Displays the list of Wi-SUN FAN router nodes which have joined to the Wi-SUN FAN border router device.
    
            > connecteddevices
            List of connected devices currently in routing table:
            fd00:7283:7e00:0:212:4b00:1ca1:727a
            fd00:7283:7e00:0:212:4b00:1ca6:17ea
            Done
            """
            try:
                # Only valid for BR
                if self.prop_get_value(SPINEL.PROP_NET_ROLE) != 0:
                    print("Error: Device role must be Border Router to process this command")
                    return
                router_state = self.prop_get_value(SPINEL.PROP_NET_STATE)
                if router_state < 5:
                    print("Error: Device must be in join state 5 (Successfully joined and operational) to process this command")
                    return
    
                num_addrs = 0
                last_block = False
                print("List of connected devices currently in routing table:")
                while (not last_block):
                    value = self.prop_get_value(SPINEL.PROP_CONNECTED_DEVICES)
                    if value is None:
                        print("Error: Could not retrieve connected devices from embedded device")
                        return
                    # Break the byte stream into different entries
                    last_block = True if (value[0] >> 7) == 1 else False
                    addrEntries = [value[i:i + IPV6_ADDR_LEN] for i in range(1, len(value), IPV6_ADDR_LEN)]
                    try:
                        with open("./spinel_conn_dev_filter.json", "r") as fp:
                            filter_json = json.load(fp)
                            addrEntries[:] = [addr for addr in addrEntries if str(ipaddress.IPv6Address(addr)) not in filter_json]
                    except:
                        # If file does not exist or cannot be opened, move on without filtering
                        pass
                    for addrEntry in addrEntries:
                        print(ipaddress.IPv6Address(addrEntry))
                        num_addrs += 1
    
                if (num_addrs == 0):
                    print("No nodes currently in routing table.")
                else:
                    print("Number of connected devices: %d" % num_addrs)
                print("Done")
            except:
                print("Fail")
                print(traceback.format_exc())
    
        def do_dodagroute(self, line):
            """
            Displays the full routing path to a device with a specified IPv6 address. Also displays the path cost.
    
            > dodagroute fd00:7283:7e00:0:212:4b00:10:50d0
            Path cost: 2
            fd00:7283:7e00:0:212:4b00:10:50d4
            fd00:7283:7e00:0:212:4b00:1ca1:758e
            fd00:7283:7e00:0:212:4b00:10:50d0
            Done
            """
            try:
                params = line.split(" ")
                try:
                    ipaddr = ipaddress.IPv6Address(params[0])
                except:
                    print("Error: Invalid IPv6 address")
                    return
    
                # Only valid for BR
                if self.prop_get_value(SPINEL.PROP_NET_ROLE) != 0:
                    print("Error: Device role must be Border Router to process this command")
                    return
                router_state = self.prop_get_value(SPINEL.PROP_NET_STATE)
                if router_state < 5:
                    print("Error: Device must be in join state 5 (Successfully joined and operational) to process this command")
                    return
    
                set_value = self.prop_set_value(SPINEL.PROP_DODAG_ROUTE_DEST, ipaddr.packed, str(len(ipaddr.packed)) + 's')
                value = self.prop_get_value(SPINEL.PROP_DODAG_ROUTE)
                if set_value is None or value is None or len(value) == 0:
                    print("Error: Could not retrieve dodag route to selected embedded device")
                    return
    
                path_cost = value[0]
                if path_cost == 0:
                    print("No path to device with specified IPv6 address")
                    return
    
                # Break the byte stream into different entries
                print("Path cost: %d" % value[0])
                addrEntries = [value[i:i + IPV6_ADDR_LEN] for i in range(1, len(value), IPV6_ADDR_LEN)]
                for addrEntry in addrEntries:
                    print(ipaddress.IPv6Address(addrEntry))
                print("Done")
            except:
                print("Fail")
                print(traceback.format_exc())
    
        def do_ipv6addresstable(self, line):
            """
            ipv6addresstable
    
                Display the Globally Unique DHCP address and Link Local Adress along with
                prefix length, valid lifetime and preferred lifetime
    
                >ipv6addresstable
    
                fd00:7283:7e00:0:212:4b00:1ca1:9463; prefix_len = 64; valid_lifetime = 84269; preferred_lifetime = 41069
                fe80::212:4b00:1ca1:9463; prefix_len = 64; valid_lifetime = 4294967295; preferred_lifetime = 4294967295
                Done
            """
            value = self.prop_get_value(SPINEL.PROP_IPV6_ADDRESS_TABLE)
            ipv6AddrTableList = self._parse_ipv6addresstable_property(value)
            for i in range(0,len(ipv6AddrTableList)):
                print(str(ipv6AddrTableList[i]["ipv6Addr"]) + "; prefix_len = " + str(ipv6AddrTableList[i]["prefixLen"]) + "; valid_lifetime = " + str(ipv6AddrTableList[i]["validLifeTime"]) + "; preferred_lifetime = " + str(ipv6AddrTableList[i]["prefferedLifeTime"]))
    
            print("Done")
    
        #reset cmd
        def do_reset(self, line):
            """
            reset
    
                Reset the NCP.
    
                > reset
            """
            self.wpan_api.cmd_reset()
            self.clear_routing_table()
    
        def do_nverase(self, line):
            """
            nverase
    
                Erase the NV memory on NCP.
    
                > nverase
            """
            self.wpan_api.cmd_nverase()
    
        # other definitions
    
        def _notify_simulator(self):
            """
            notify the simulator that there are no more UART data for the current command.
            """
            OT_SIM_EVENT_POSTCMD = 4
    
            message = struct.pack('=QBHB', 0, OT_SIM_EVENT_POSTCMD, 1,
                                  int(self.nodeid))
            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            sock.bind(self._addr)
            sock.sendto(message, self._simulator_addr)
            sock.close()
    
        def postcmd(self, stop, line):
            if self.VIRTUAL_TIME:
                self._notify_simulator()
            return stop
    
    
    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("-b",
                              "--baudrate",
                              action="store",
                              dest="baudrate",
                              type="int",
                              default=DEFAULT_BAUDRATE)
        opt_parser.add_option("--rtscts",
                              action="store_true",
                              dest="rtscts",
                              default=False),
        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_true",
                              dest="verbose")
        opt_parser.add_option("-d",
                              "--debug",
                              action="store",
                              dest="debug",
                              type="int",
                              default=CONFIG.DEBUG_ENABLE)
        opt_parser.add_option("--vendor-path",
                              action="store",
                              dest="vendor_path",
                              type="string")
    
        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)
    
        # Obtain the vendor module path, if provided
        if not options.vendor_path:
            options.vendor_path = os.environ.get("SPINEL_VENDOR_PATH")
    
        if options.vendor_path:
            options.vendor_path = os.path.abspath(options.vendor_path)
            vendor_path, vendor_module = os.path.split(options.vendor_path)
            sys.path.insert(0, vendor_path)
        else:
            vendor_module = "vendor"
    
        # Set default stream to pipe
        stream_type = 'p'
        stream_descriptor = "../../examples/apps/ncp/ot-ncp-ftd " + 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, options.verbose,
                            options.baudrate, options.rtscts)
        try:
            vendor_ext = importlib.import_module(vendor_module + '.vendor')
            cls = type(vendor_ext.VendorSpinelCliCmd.__name__,
                       (SpinelCliCmd, vendor_ext.VendorSpinelCliCmd), {})
            shell = cls(stream, nodeid=options.nodeid, vendor_module=vendor_module)
            #print(" no exception occurred ")
        except ImportError:
            shell = SpinelCliCmd(stream,
                                 nodeid=options.nodeid,
                                 vendor_module=vendor_module)
            #print(" in exception")
    
        try:
            shell.cmdloop()
        except KeyboardInterrupt:
            CONFIG.LOGGER.info('\nCTRL+C Pressed')
    
        if shell.wpan_api:
            shell.wpan_api.stream.close()
    
    
    if __name__ == "__main__":
        main()


    It implements sending a confirmable message from the CoAP node to the border router when any of the two buttons is pressed. The message carries a 1024 byte payload (value of each byte = 0xF). When the message is received the length of the message is print to the terminal and an acknowledgement carrying the payload "Hello World!" is sent to the CoAP node. After receiving the acknowledgement the payload of the acknowledgement is written to the defined UART and the red LED blinks two times.

    Kind regards,
    Theo

  • Hi Marie and Theo,

    Thank you for the quick responses! I could do a few more tests based on your replies and it looks like the stack size of the Thread was not big enough to handle all the application. I have increased it and it seems to be working now, I can already send bigger payloads.

    Thank you.

    Regards,
    Eduardo.

  • Hi Eduardo,

    great that it works now.

    Kind regards,
    Theo