This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

thread_mqtt_sn_client_publisher for dongle (pca10059)

Hello,

I try to transfer the thread_mqtt_sn_client_publisher example from the pca10056 to the pca10059. 

Before that, I tried out the example with the pca10056, a boarder router (RPi + pca10059) and homeassistant server. I was able to send a message via mqttsn by pressing a button on the DK to homeassistant. Everthing worked as expected.

Now I want to do the same with the pca10059. But since I have not enough buttons on the dongle, I tried to connect to the network within my main with some time to wait for each step and then periodically publish a message:

#include <stdbool.h>
#include <stdint.h>
#include <string.h>

#include "app_scheduler.h"
#include "app_timer.h"
#include "bsp_thread.h"
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"
#include "nrf_delay.h"

#include "mqttsn_client.h"
#include "thread_utils.h"

#include <openthread/thread.h>

#define SEARCH_GATEWAY_TIMEOUT 5                                            /**< MQTT-SN Gateway discovery procedure timeout in [s]. */

#define SCHED_QUEUE_SIZE       32                                           /**< Maximum number of events in the scheduler queue. */
#define SCHED_EVENT_DATA_SIZE  APP_TIMER_SCHED_EVENT_DATA_SIZE              /**< Maximum app_scheduler event size. */

static mqttsn_client_t      m_client;                                       /**< An MQTT-SN client instance. */
static mqttsn_remote_t      m_gateway_addr;                                 /**< A gateway address. */
static uint8_t              m_gateway_id;                                   /**< A gateway ID. */
static mqttsn_connect_opt_t m_connect_opt;                                  /**< Connect options for the MQTT-SN client. */
static uint8_t              m_led_state        = 0;                         /**< Previously sent BSP_LED_2 command. */
static uint16_t             m_msg_id           = 0;                         /**< Message ID thrown with MQTTSN_EVENT_TIMEOUT. */
static char                 m_client_id[]      = "nRF52840_publisher";      /**< The MQTT-SN Client's ID. */
static char                 m_topic_name[]     = "nRF52840_resources/led3"; /**< Name of the topic corresponding to subscriber's BSP_LED_2. */
static mqttsn_topic_t       m_topic            =                            /**< Topic corresponding to subscriber's BSP_LED_2. */
{
    .p_topic_name = (unsigned char *)m_topic_name,
    .topic_id     = 0,
};

/***************************************************************************************************
 * @section MQTT-SN
 **************************************************************************************************/

/**@brief Turns the MQTT-SN network indication LED on.
 *
 * @details This LED is on when an MQTT-SN client is in connected or awake state.
 */
static void light_on(void)
{
    LEDS_ON(BSP_LED_3_MASK);
}


/**@brief Turns the MQTT-SN network indication LED off.
 *
 * @details This LED is on when an MQTT-SN client is in disconnected or asleep state.
 */
static void light_off(void)
{
    LEDS_OFF(BSP_LED_3_MASK);
}

/**@brief Initializes MQTT-SN client's connection options.
 */
static void connect_opt_init(void)
{
    m_connect_opt.alive_duration = MQTTSN_DEFAULT_ALIVE_DURATION,
    m_connect_opt.clean_session  = MQTTSN_DEFAULT_CLEAN_SESSION_FLAG,
    m_connect_opt.will_flag      = MQTTSN_DEFAULT_WILL_FLAG,
    m_connect_opt.client_id_len  = strlen(m_client_id),

    memcpy(m_connect_opt.p_client_id, (unsigned char *)m_client_id, m_connect_opt.client_id_len);
}


/**@brief Processes GWINFO message from a gateway.
 *
 * @details This function updates MQTT-SN Gateway information.
 *
 * @param[in]    p_event  Pointer to MQTT-SN event.
 */
static void gateway_info_callback(mqttsn_event_t * p_event)
{
    m_gateway_addr = *(p_event->event_data.connected.p_gateway_addr);
    m_gateway_id   = p_event->event_data.connected.gateway_id;
}


/**@brief Processes CONNACK message from a gateway.
 *
 * @details This function launches the topic registration procedure if necessary.
 */
static void connected_callback(void)
{
    light_on();

    uint32_t err_code = mqttsn_client_topic_register(&m_client,
                                                     m_topic.p_topic_name,
                                                     strlen(m_topic_name),
                                                     &m_msg_id);
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_ERROR("REGISTER message could not be sent. Error code: 0x%x\r\n", err_code);
    }
}


/**@brief Processes DISCONNECT message from a gateway. */
static void disconnected_callback(void)
{
    light_off();
}


/**@brief Processes REGACK message from a gateway.
 *
 * @param[in] p_event Pointer to MQTT-SN event.
 */
static void regack_callback(mqttsn_event_t * p_event)
{
    m_topic.topic_id = p_event->event_data.registered.packet.topic.topic_id;
    NRF_LOG_INFO("MQTT-SN event: Topic has been registered with ID: %d.\r\n",
                 p_event->event_data.registered.packet.topic.topic_id);
}


/**@brief Processes retransmission limit reached event. */
static void timeout_callback(mqttsn_event_t * p_event)
{
    NRF_LOG_INFO("MQTT-SN event: Timed-out message: %d. Message ID: %d.\r\n",
                  p_event->event_data.error.msg_type,
                  p_event->event_data.error.msg_id);
}


/**@brief Processes results of gateway discovery procedure. */
static void searchgw_timeout_callback(mqttsn_event_t * p_event)
{
    NRF_LOG_INFO("MQTT-SN event: Gateway discovery result: 0x%x.\r\n", p_event->event_data.discovery);
}


/**@brief Function for handling MQTT-SN events. */
void mqttsn_evt_handler(mqttsn_client_t * p_client, mqttsn_event_t * p_event)
{
    switch(p_event->event_id)
    {
        case MQTTSN_EVENT_GATEWAY_FOUND:
            NRF_LOG_INFO("MQTT-SN event: Client has found an active gateway.\r\n");
            gateway_info_callback(p_event);
            break;

        case MQTTSN_EVENT_CONNECTED:
            NRF_LOG_INFO("MQTT-SN event: Client connected.\r\n");
            connected_callback();
            break;

        case MQTTSN_EVENT_DISCONNECT_PERMIT:
            NRF_LOG_INFO("MQTT-SN event: Client disconnected.\r\n");
            disconnected_callback();
            break;

        case MQTTSN_EVENT_REGISTERED:
            NRF_LOG_INFO("MQTT-SN event: Client registered topic.\r\n");
            regack_callback(p_event);
            break;

        case MQTTSN_EVENT_PUBLISHED:
            NRF_LOG_INFO("MQTT-SN event: Client has successfully published content.\r\n");
            break;

        case MQTTSN_EVENT_TIMEOUT:
            NRF_LOG_INFO("MQTT-SN event: Retransmission retries limit has been reached.\r\n");
            timeout_callback(p_event);
            break;

        case MQTTSN_EVENT_SEARCHGW_TIMEOUT:
            NRF_LOG_INFO("MQTT-SN event: Gateway discovery procedure has finished.\r\n");
            searchgw_timeout_callback(p_event);
            break;

        default:
            break;
    }
}

/***************************************************************************************************
 * @section State
 **************************************************************************************************/

static void state_changed_callback(uint32_t flags, void * p_context)
{
    NRF_LOG_INFO("State changed! Flags: 0x%08x Current role: %d\r\n",
                 flags, otThreadGetDeviceRole(p_context));
}

/***************************************************************************************************
 * @section Buttons
 **************************************************************************************************/

static void publish(void)
{
    uint32_t err_code = mqttsn_client_publish(&m_client, m_topic.topic_id, "Hello", 1, &m_msg_id);
    if (err_code != NRF_SUCCESS)
    {
        NRF_LOG_ERROR("PUBLISH message could not be sent. Error code: 0x%x\r\n", err_code)
    }
}

/***************************************************************************************************
 * @section Initialization
 **************************************************************************************************/

/**@brief Function for initializing the Application Timer Module.
 */
static void timer_init(void)
{
    uint32_t err_code = app_timer_init();
    APP_ERROR_CHECK(err_code);
}


/**@brief Function for initializing the LEDs.
 */
static void leds_init(void)
{
    LEDS_CONFIGURE(LEDS_MASK);
    LEDS_OFF(LEDS_MASK);
}


/**@brief Function for initializing the nrf log module.
 */
static void log_init(void)
{
    ret_code_t err_code = NRF_LOG_INIT(NULL);
    APP_ERROR_CHECK(err_code);

    NRF_LOG_DEFAULT_BACKENDS_INIT();
}

/**@brief Function for initializing the Thread Stack.
 */
static void thread_instance_init(void)
{
    thread_configuration_t thread_configuration =
    {
        .radio_mode        = THREAD_RADIO_MODE_RX_ON_WHEN_IDLE,
        .autocommissioning = true,
        .autostart_disable = false,
    };

    thread_init(&thread_configuration);
    thread_cli_init();
    thread_state_changed_callback_set(state_changed_callback);
}


/**@brief Function for initializing the MQTTSN client.
 */
static void mqttsn_init(void)
{
    uint32_t err_code = mqttsn_client_init(&m_client,
                                           MQTTSN_DEFAULT_CLIENT_PORT,
                                           mqttsn_evt_handler,
                                           thread_ot_instance_get());
    APP_ERROR_CHECK(err_code);

    connect_opt_init();
}


/**@brief Function for initializing scheduler module.
 */
static void scheduler_init(void)
{
    APP_SCHED_INIT(SCHED_EVENT_DATA_SIZE, SCHED_QUEUE_SIZE);
}

int main(int argc, char *argv[])
{
    log_init();
    scheduler_init();
    timer_init();
    leds_init();    

    thread_instance_init();
    thread_bsp_init();
    mqttsn_init();
    nrf_delay_ms(10000);

    
    mqttsn_client_search_gateway(&m_client, SEARCH_GATEWAY_TIMEOUT);
    nrf_delay_ms(10000);

    mqttsn_client_connect(&m_client, &m_gateway_addr, m_gateway_id, &m_connect_opt);
    nrf_delay_ms(10000);

    while (true)
    {
        thread_process();
        app_sched_execute();

        if (NRF_LOG_PROCESS() == false)
        {
            thread_sleep();
        }
        nrf_delay_ms(2000);
        publish();
    }
}

Unfortunately, I dont receive any input, when I start listining to the topic in homeassistant.

What do I have to consider, when transferring the code to the dongle.

I changed everything in the makefile and linker script to fit the specifications of the dongle.

Regards

Maboo

Ps: I use Linux, SDK17, VS-Code, pca10059

Parents
  • Hello,

    I assume you have not tried to debug the dongle, right? The dongle doesn't have an onboard debugger, so you would have to solder on a debug connector, and attach an external debugger (e.g. the pca10056) to do so.

    Please be aware of the risks to brick the device when you attach a debugger, as described here:

    https://devzone.nordicsemi.com/nordic/short-range-guides/b/getting-started/posts/nrf52840-dongle-programming-tutorial

    My initial guess is that this has to do with the bootloader in the dongle. By default, the bootloader in the dongle takes up  0x000E 0000 -> 0x0010 0000.

    In general, the nRF52 dongle is not a development tool, mainly because of the lack of debugger. If you need to use the dongle, I suggest that you try the following:

    1: Flash a serial bootloader at the start address 0x000E0000 on the DK (pca10056), and flash the example on the DK. Check whether it is working, and debug if it is not working. 

    2: if 1) succeeds, then try to port the project to the nRF52 Dongle, but keep running it on the DK, and monitor the behavior again. If it doesn't work, does the log say anything?

    Best regards,

    Edvin

  • Hi Edvin,

    thanks for your reply.

    I didn't debug using a external debugger (I don't have one), but I used that example to port the pca10056 version to the pca10059.

    Within my linkerfile I have now the following memory config:

    /* Linker script to configure memory regions. */
    
    SEARCH_DIR(.)
    GROUP(-lgcc -lc -lnosys)
    
    MEMORY
    {  
      FLASH (rx) : ORIGIN = 0x1000, LENGTH = 0xdf000
      RAM (rwx) :  ORIGIN = 0x20000008, LENGTH = 0x3fff8
      ot_flash_data (r) : ORIGIN = 0xfc000, LENGTH = 0x4000
    }
    
    SECTIONS
    {
      . = ALIGN(4);
      .ot_flash_data :
      {
        PROVIDE(__start_ot_flash_data = ORIGIN(ot_flash_data));
        KEEP(*(SORT(.ot_flash_data*)))
        PROVIDE(__stop_ot_flash_data = ORIGIN(ot_flash_data) + LENGTH(ot_flash_data));
      } > ot_flash_data
    }
    
    SECTIONS
    {
      . = ALIGN(4);
      .mem_section_dummy_ram :
      {
      }
      .log_dynamic_data :
      {
        PROVIDE(__start_log_dynamic_data = .);
        KEEP(*(SORT(.log_dynamic_data*)))
        PROVIDE(__stop_log_dynamic_data = .);
      } > RAM
      .log_filter_data :
      {
        PROVIDE(__start_log_filter_data = .);
        KEEP(*(SORT(.log_filter_data*)))
        PROVIDE(__stop_log_filter_data = .);
      } > RAM
    
    } INSERT AFTER .data;
    
    SECTIONS
    {
      .mem_section_dummy_rom :
      {
      }
      .pwr_mgmt_data :
      {
        PROVIDE(__start_pwr_mgmt_data = .);
        KEEP(*(SORT(.pwr_mgmt_data*)))
        PROVIDE(__stop_pwr_mgmt_data = .);
      } > FLASH
      .log_const_data :
      {
        PROVIDE(__start_log_const_data = .);
        KEEP(*(SORT(.log_const_data*)))
        PROVIDE(__stop_log_const_data = .);
      } > FLASH
      .log_backends :
      {
        PROVIDE(__start_log_backends = .);
        KEEP(*(SORT(.log_backends*)))
        PROVIDE(__stop_log_backends = .);
      } > FLASH
        .nrf_balloc :
      {
        PROVIDE(__start_nrf_balloc = .);
        KEEP(*(.nrf_balloc))
        PROVIDE(__stop_nrf_balloc = .);
      } > FLASH
    
    } INSERT AFTER .text
    
    ASSERT(__start_ot_flash_data > __etext, "Section ot_flash_data overlaps text section")
    
    INCLUDE "nrf_common.ld"
    

    So where do I see the location of the bootloader?

          

    1: Flash a serial bootloader at the start address 0x000E0000 on the DK (pca10056), and flash the example on the DK. Check whether it is working, and debug if it is not working. 

    I am no expert here, so I tried to figure out how to do it and I came up with this. But I am stuck a bit. So, how do you flash a bootloader on a specific memory region (0x000E 0000 -> 0x0010 0000)?

    Best regards,

    Maboo

  • Hi Edvin,

    you were completely right. It seems the flash region of OT was the failure. Now i can connect to the network.

    I just have one last question. CLI doesn't work on the dongle, so I can't change e.g. the panID like in the Thread_CLI_example. I had to change it within the border router config to fit the net parameters of the dongle.

    Do you have an idea what the problem can be? Or is it possible to change the network parameters on the dongle before I flash it? 

    Best regards

    Maboo

  • What example project are you using for your dongle? Is it the USB or UART CLI example? If it is the UART, I suggest you test the USB variant, such as:

    SDK\examples\thread\cli\ftd\usb\pca10059\

    Are you able to interface it with the USB port? Are you able to see any return value from when you try to change the panID?

    I guess you can also set it in sdk_config.h. Have you tried this?

    Best regards,

    Edvin

  • I used the mqttsn_client_publisher example. And the idea is, I have a border router with your firmware and the dongle publishing a string via the router to Home-assistant after I press a button.

    Now I manually connected the router with the dongle as described before: I connect myself to the router via wifi, open the OpenThread web GUI and join the dongle. Now I can send the string.

    But eventually I want to use an external commissioner like the thread app on my phone. Therefore I have to access the dongle to find out the EUI of the joiner (dongle). But here is the problem, I cant access via putty - Unable to open the connection to /dev/tty... . If I use the same code on the DK, it works perfectly.

    I have enabled UART and RTT in the sdk_config.h

  • The dongle's usb pins aren't connected to UART. In fact, it is not possible to move the USB pins. They are fixed in HW. Use the same implementation that is used in the example.

    SDK\examples\thread\cli\ftd\usb\pca10059. 

    You can even test it and see that the CLI is working over USB there.

    So to clarify, the USB pinout on the dongle is not the same as the programming usb port on the DK. On the DK, the default uart pins are routed through the programmer back to the USB cable, while the USB peripheral on the DK (the one located on the long side) is the same usb port that is present on the dongle.

    BR,
    Edvin

  • Thanks for the quick response. I understand the problem with the HW USB pins on the dongle. But what do you mean by:

    Use the same implementation that is used in the example.

    Where can I find the usb implementation for the dongle? Do you mean the sdk_config?

Reply Children
Related