Skip to content
Manuel Bl edited this page Aug 2, 2020 · 5 revisions

hello_world

hello_world is a simple example app that sends the message "Hello, world" every 30 seconds and displays received messages.

It can be found in GitHub at https://github.com/manuelbl/ttn-esp32/tree/master/examples/hello_world. The relevant code is in main.cpp.

Notable code

The start of the code is standard and mainly about configuring the device. There are pin configurations:

// Pins and other resources
#define TTN_SPI_HOST      HSPI_HOST
#define TTN_SPI_DMA_CHAN  1
#define TTN_PIN_SPI_SCLK  5
...
#define TTN_PIN_DIO1      33

app_main() starts with the initialization of all resources that are possibly shared between the TTN code and other parts of the app: GPIO ISR handler service, NVS and SPI bus:

esp_err_t err;
// Initialize the GPIO ISR handler service
err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
ESP_ERROR_CHECK(err);

// Initialize the NVS (non-volatile storage) for saving and restoring the keys
err = nvs_flash_init();
ESP_ERROR_CHECK(err);

// Initialize SPI bus
spi_bus_config_t spi_bus_config;
spi_bus_config.miso_io_num = TTN_PIN_SPI_MISO;
spi_bus_config.mosi_io_num = TTN_PIN_SPI_MOSI;
spi_bus_config.sclk_io_num = TTN_PIN_SPI_SCLK;
spi_bus_config.quadwp_io_num = -1;
spi_bus_config.quadhd_io_num = -1;
spi_bus_config.max_transfer_sz = 0;
err = spi_bus_initialize(TTN_SPI_HOST, &spi_bus_config, TTN_SPI_DMA_CHAN);
ESP_ERROR_CHECK(err);

Then the pins of the TTN device are configured (using the defines at the top of the file):

// Configure the SX127x pins
ttn.configurePins(TTN_SPI_HOST, TTN_PIN_NSS, TTN_PIN_RXTX, TTN_PIN_RST, TTN_PIN_DIO0, TTN_PIN_DIO1);

After provisioning the keys and joining the network, a separate task is started to send messages:

xTaskCreate(sendMessages, "send_messages", 1024 * 4, (void* )0, 3, NULL);

The task sends a messages, waits 30 seconds and repeats forever.

To receive messages, a callback function is registered in app_main():

ttn.onMessage(messageReceived);

The registered function prints the message:

void messageReceived(const uint8_t* message, size_t length, port_t port)
{
    printf("Message of %d bytes received on port %d:", length, port);
    for (int i = 0; i < length; i++)
        printf(" %02x", message[i]);
    printf("\n");
}

provision

Adding the EUIs and keys to the source code is unfortunate:

  • Sensitive data might be put into a source code repository several people have access to
  • The same EUIs and keys might accidently be used on a second device and disrupting the communication of the first device
  • The source code needs to be changed and recompiled for each device

As an alternative, ttn-esp32 can run a background process that listens for AT commands. The EUIs and keys can then be provisions by AT commands.

The example can be found in GitHub at https://github.com/manuelbl/ttn-esp32/tree/master/examples/provision. The relevant code is in main.cpp.

Notable code

Compared to the hello_world example, it replaces the ttn.provision... line in the with:

ttn.startProvisioningTask();

ttn.waitForProvisioning();

The first line starts the background task, and the second one waits until the provisioning keys have been provided. It immediately returns, if keys are found in the non-volatile storage.

Compared to the hello_world example, the provision example also removes the lines with the EUIs and keys.

When you flash the device and start it for the first time, the following output appears:

# make flash monitor
...
I (273) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (463) ttn_hal: IO initialized
I (473) ttn_hal: SPI initialized
I (473) ttn_hal: Timer initialized
I (513) uart: queue free spaces: 20
I (513) ttn_prov: Provisioning task started

You can then set the EUIs and key via a AT commands:

AT+PROV=0123456789ABCDEF-0123456789ABCDEF-0123456789ABCDEF0123456789ABCDEF
I (77513) ttn_prov: Dev and app EUI and app key saved in NVS storage
I (77513) ttn: event EV_RESET
OK
I (78513) ttn: Device successfully provisioned
Joining...
I (78513) ttn: event EV_JOINING

The next time the device is started, it immediately join TTN and doesn't require any EUIs and keys anymore as they have been saved in NVS.

monitoring

monitoring is a simple example app that prints the applied RF settings.

It can be found in GitHub at https://github.com/manuelbl/ttn-esp32/tree/master/examples/monitoring. The relevant code is in main.cpp.

Notable code

Every time a message has been sent, the RF settings for the transmission and the receive windows 1 and 2 are printed using the below functions:

void printRFSettings(const char* window, const TTNRFSettings& settings)
{
    int bw = (1 << (static_cast<int>(settings.bandwidth) - 1)) * 125;
    int sf = static_cast<int>(settings.spreadingFactor) + 5;

    if (settings.spreadingFactor == kTTNSFNone)
    {
        printf("%s: not used\n", window);
    }
    else if (settings.spreadingFactor == kTTNFSK)
    {
        printf("%s: FSK, BW %dkHz, %d.%d MHz\n",
                window, bw, settings.frequency / 1000000, (settings.frequency % 1000000 + 50000) / 100000);
    }
    else
    {
        printf("%s: SF%d, BW %dkHz, %d.%d MHz\n",
                window, sf, bw, settings.frequency / 1000000, (settings.frequency % 1000000 + 50000) / 100000);
    }
}

void printAllRFSettings()
{
    printRFSettings("TX ", ttn.txSettings());
    printRFSettings("RX1", ttn.rx1Settings());
    printRFSettings("RX2", ttn.rx2Settings());
}

In addition, if a message has been received, the RSSI value is printed:

printf("RSSI: %d dBm\n", ttn.rssi());
Clone this wiki locally