The ability to control and configure IoT devices remotely is becoming important as it provides several benefits such as:
- Convenience: Users can access and control their devices from anywhere, at any time, through an internet-connected device.
- Increased Efficiency: Remote management allows for real-time monitoring and control, reducing the need for manual intervention and increasing efficiency.
- Improved Security: By being able to configure and update devices remotely, it is possible to quickly and effectively address security vulnerabilities, reducing the risk of compromise.
- Scalability: With remote management, it becomes easier to manage large numbers of devices, especially in industrial and enterprise environments, where a large number of IoT devices are deployed.
- Cost Savings: By reducing the need for manual intervention, remote management can help to reduce the cost of maintenance and support.
Overall, the ability to control and configure IoT devices remotely is a crucial aspect of the IoT ecosystem, enabling users to take full advantage of the capabilities of these devices, while also providing greater flexibility, security, and efficiency.
One such platform is iotcreators. The goal of IoT Creators is to make the implementation of an IoT end-to-end solution easier for all the stakeholders. It enables the hardware developers and integrators to transfer data through all infrastructure and software layers from the main application without having to worry about IoT network technologies and protocols such as NB-IOT, LTE-M, LoRaWAN, MQTT, LWM2M, etc.
This tutorial…Covers how to connect the Conexio Stratus device which is an nRF9160-based cellular IoT kit to the IoTCreators platform with ease.
Specifically, this post will demonstrate how to:
- Register your device to the IoTCreators platform.
- Connect the Stratus kit to the IoTCreators cloud.
- Send and decode environmental data.
- Remotely control device peripherals, for example, LEDs using downward commands.
Wi-Fi service might not be the most reliable and even not an option in some places for users. For instance, if you want to monitor and deploy a sensor device in the middle of the farm, where there is no WiFi, but you have good cellular coverage. This is where cellular connectivity comes in handy and which is why I decided to go with the cellular-based Conexio Stratus dev kit. Although there are many other IoT platforms that are cellular-based, however, following are some of the reasons why Conexio Stratus stands out from the crowd.
- The Conexio Stratus dev kit allows us to easily prototype an IoT solution as it comes with a prepaid 500MB of data with 10 years of global cellular service. No contract is required.
- The dev kit supports LTE-M and NB-IoT protocols and has integrated GPS as well, eliminating the need to buy and integrate an external GPS module.
- It also has onboard environmental sensors, specifically, Sensirion Sht4x temperature and humidity sensor and LIS2DH accelerometer from ST micro.
- Last but not least, the kit supports energy harvesting and battery recharging from solar. This is one of the missing features that is currently not found in many IoT devices.
Notes: (1) This tutorial assumes that one has already installed the nRF Connect SDK v2.1.1 and the prerequisites for the Conexio Stratus device. See our previous posts as to how to complete those. (2) You have received the IoTCreators Starterkit sim card.
First, download the full sample application for this project from the conexio-stratus-firmware repo on GitHub. This sample application performs a few different tasks:
- Configures the correct modem Access Point Name (APN): scs.telekom.tma.iot.
- Setups up and initializes the UDP server for bidirectional communication to the IoTCreators cloud.
- In Active mode, the application sends environmental data [temp & humidity] every 60 seconds to the IoT Creators platform.
- Periodically checks for a downward/downlink command to control the onboard LED, i.e., to either turn it ON or OFF.
Here is the full main application:
/*--------------------------------------------------------------------------*/
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
* Copyright (c) 2022 IoT Creators
* Copyright (c) 2023 Conexio Technologies, Inc
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
/*--------------------------------------------------------------------------*/
#include <zephyr.h>
#include <stdio.h>
#include <modem/lte_lc.h>
#include <nrf_modem_at.h>
#include <modem/modem_info.h>
#include <net/socket.h>
#include <device.h>
#include <drivers/sensor.h>
#include <drivers/sensor/sht4x.h>
#include <devicetree.h>
#include <drivers/gpio.h>
#include <logging/log.h>
/*--------------------------------------------------------------------------*/
LOG_MODULE_REGISTER(cloud_client, CONFIG_UDP_SAMPLE_LOG_LEVEL);
/*--------------------------------------------------------------------------*/
#define MODEM_APN "scs.telekom.tma.iot"
#define TEMP_CALIBRATION_OFFSET 3
#define RECV_BUF_SIZE 2048
#define RCV_POLL_TIMEOUT_MS 1000 /* Milliseconds */
/*--------------------------------------------------------------------------*/
/* Sensor data */
static struct sensor_value temp, hum;
const struct device *sht;
#if !DT_HAS_COMPAT_STATUS_OKAY(sensirion_sht4x)
#error "No sensirion,sht4x compatible node found in the device tree"
#endif
/*--------------------------------------------------------------------------*/
/* The devicetree node identifier for the Blue LED "led0" alias. */
#define LED0_NODE DT_ALIAS(led0)
static const struct gpio_dt_spec led_0 = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
#if DT_NODE_HAS_STATUS(LED0_NODE, okay)
#define LED0 DT_GPIO_LABEL(LED0_NODE, gpios)
#define PIN DT_GPIO_PIN(LED0_NODE, gpios)
#else
/* A build error here means your board isn't set up to blink an LED. */
#error "Unsupported board: led0 devicetree alias is not defined"
#define LED0 ""
#define PIN 0
#endif
/*--------------------------------------------------------------------------*/
/* UDP Socket */
static int client_fd;
static bool socket_open = false;
static struct sockaddr_storage host_addr;
/* Workqueues */
static struct k_work_delayable server_transmission_work;
static struct k_work_delayable data_fetch_work;
static struct k_work_delayable poll_data_work;
K_SEM_DEFINE(lte_connected, 0, 1);
struct modem_param_info modem_param;
static char recv_buf[RECV_BUF_SIZE];
/*--------------------------------------------------------------------------*/
static void fetch_sensor_data(const struct device *sensor)
{
if (sensor_sample_fetch(sensor)) {
LOG_ERR("Failed to fetch sample from SHT4X device");
return;
}
sensor_channel_get(sensor, SENSOR_CHAN_AMBIENT_TEMP, &temp);
sensor_channel_get(sensor, SENSOR_CHAN_HUMIDITY, &hum);
}
/*--------------------------------------------------------------------------*/
static void transmit_udp_data(char *data, size_t len)
{
int err;
if (data != NULL){
LOG_INF("Sending UDP payload, length: %u, data: %s", len, data);
err = send(client_fd, data, len, 0);
if (err < 0) {
LOG_ERR("Failed to transmit UDP packet, %d", errno);
}
}
}
/*--------------------------------------------------------------------------*/
int receive_udp_data(char *buf, int buf_size)
{
int bytes;
bytes = recv(client_fd, buf, buf_size, 0);
if (bytes < 0) {
LOG_ERR("recv() failed, err %d", errno);
}
else if (bytes > 0) {
// Make sure buf is NULL terminated (for safe use)
if (bytes < buf_size) {
buf[bytes] = '\0';
} else {
buf[buf_size - 1] = '\0';
}
LOG_INF("Received UDP data, length: %u, data: %s", bytes, buf);
return bytes;
}
return 0;
}
/*--------------------------------------------------------------------------*/
/* Event Handler - used when data received via UDP */
static void udp_evt_handler(char *buf)
{
LOG_INF("Handling UDP data, data: %s", buf);
if (strcmp(buf, "led-on") == 0) {
/* Turns Blue LED ON */
gpio_pin_set(led_0.port, PIN, 1);
}
else if (strcmp(buf, "led-off") == 0){
/* Turns Blue LED OFF */
gpio_pin_set(led_0.port, PIN, 0);
}
else
LOG_ERR("Received invalid command/string");
}
/*--------------------------------------------------------------------------*/
static void data_fetch_work_fn(struct k_work *work)
{
/* Update sensor + modem data */
fetch_sensor_data(sht);
modem_info_params_get(&modem_param);
/* Reschedule work task */
k_work_schedule(&data_fetch_work, K_SECONDS(CONFIG_UDP_DATA_UPLOAD_FREQUENCY_SECONDS));
}
/*--------------------------------------------------------------------------*/
static void initial_data_transmission(void)
{
/* Get current modem parameters */
modem_info_params_get(&modem_param);
char data_output[128];
char imei[16];
char operator[8];
modem_info_string_get(MODEM_INFO_IMEI, imei, sizeof(imei));
modem_info_string_get(MODEM_INFO_OPERATOR, operator, sizeof(operator));
/* Format network data to JSON */
sprintf(data_output, "{\"Msg\":\"Event: Stratus connected, %s, %s, %d\",\"Oper\":\"%s\",\"Bd\":%d}",
imei, operator, modem_param.network.current_band.value, operator, modem_param.network.current_band.value);
/* Transmit data via UDP Socket */
LOG_INF("Transmitting initial UDP data");
transmit_udp_data(data_output, strlen(data_output));
}
/*--------------------------------------------------------------------------*/
static void server_transmission_work_fn(struct k_work *work)
{
/* Format sensor data to JSON */
char data_output[128];
/* Format sensor data to JSON */
sprintf(data_output,"{\"Temp\":%d.%02d,\"Humid\":%d.%02d}",
temp.val1, temp.val2, hum.val1, hum.val2);
/* Transmit data via UDP Socket */
LOG_INF("Transmitting sensor data to IoTCreators cloud");
transmit_udp_data(data_output, strlen(data_output));
/* Reschedule work task */
k_work_schedule(&server_transmission_work, K_SECONDS(CONFIG_UDP_DATA_UPLOAD_FREQUENCY_SECONDS));
}
/*--------------------------------------------------------------------------*/
static void poll_data_work_fn(struct k_work *work)
{
if (socket_open){
struct pollfd fds[1];
int ret = 0;
fds[0].fd = client_fd;
fds[0].events = POLLIN;
fds[0].revents = 0;
ret = poll(fds, 1, RCV_POLL_TIMEOUT_MS);
if (ret > 0) {
int bytes;
bytes = receive_udp_data(recv_buf, RECV_BUF_SIZE);
if (bytes > 0) {
udp_evt_handler(recv_buf);
}
}
}
/* Reschedule work task for RXing data */
k_work_schedule(&poll_data_work, K_SECONDS(CONFIG_UDP_DATA_DOWNLOAD_FREQUENCY_SECONDS));
}
/*--------------------------------------------------------------------------*/
static void work_init(void)
{
k_work_init_delayable(&server_transmission_work, server_transmission_work_fn);
k_work_init_delayable(&data_fetch_work, data_fetch_work_fn);
k_work_init_delayable(&poll_data_work, poll_data_work_fn);
}
/*--------------------------------------------------------------------------*/
#if defined(CONFIG_NRF_MODEM_LIB)
static void lte_handler(const struct lte_lc_evt *const evt)
{
switch (evt->type) {
case LTE_LC_EVT_NW_REG_STATUS:
if ((evt->nw_reg_status != LTE_LC_NW_REG_REGISTERED_HOME) &&
(evt->nw_reg_status != LTE_LC_NW_REG_REGISTERED_ROAMING)) {
socket_open=false;
break;
}
LOG_INF("Network registration status: %s",
evt->nw_reg_status == LTE_LC_NW_REG_REGISTERED_HOME ?
"Connected - home network" : "Connected - roaming");
k_sem_give(<e_connected);
break;
case LTE_LC_EVT_PSM_UPDATE:
LOG_INF("PSM parameter update: TAU: %d, Active time: %d",
evt->psm_cfg.tau, evt->psm_cfg.active_time);
break;
case LTE_LC_EVT_EDRX_UPDATE: {
char log_buf[60];
ssize_t len;
len = snprintf(log_buf, sizeof(log_buf),
"eDRX parameter update: eDRX: %f, PTW: %f",
evt->edrx_cfg.edrx, evt->edrx_cfg.ptw);
if (len > 0) {
LOG_INF("%s", log_buf);
}
break;
}
case LTE_LC_EVT_RRC_UPDATE:
LOG_INF("RRC mode: %s",
evt->rrc_mode == LTE_LC_RRC_MODE_CONNECTED ?
"Connected" : "Idle");
break;
case LTE_LC_EVT_CELL_UPDATE:
LOG_INF("LTE cell changed: Cell ID: %d, Tracking area: %d",
evt->cell.id, evt->cell.tac);
break;
default:
break;
}
}
/*--------------------------------------------------------------------------*/
static int configure_low_power(void)
{
int err;
#if defined(CONFIG_UDP_PSM_ENABLE)
/* LTE Power Saving Mode */
err = lte_lc_psm_req(true);
if (err) {
LOG_ERR("lte_lc_psm_req, error: %d", err);
}
#else
err = lte_lc_psm_req(false);
if (err) {
LOG_ERR("lte_lc_psm_req, error: %d", err);
}
#endif
#if defined(CONFIG_UDP_EDRX_ENABLE)
/* Enhanced Discontinuous Reception */
err = lte_lc_edrx_req(true);
if (err) {
LOG_ERR("lte_lc_edrx_req, error: %d", err);
}
#else
err = lte_lc_edrx_req(false);
if (err) {
LOG_ERR("lte_lc_edrx_req, error: %d", err);
}
#endif
#if defined(CONFIG_UDP_RAI_ENABLE)
/** Release Assistance Indication */
err = lte_lc_rai_req(true);
if (err) {
LOG_ERR("lte_lc_rai_req, error: %d", err);
}
#endif
return err;
}
/*--------------------------------------------------------------------------*/
static void modem_init(void)
{
int err;
char response[128];
if (IS_ENABLED(CONFIG_LTE_AUTO_INIT_AND_CONNECT)) {
/* Do nothing, modem is already configured and LTE connected. */
} else {
err = lte_lc_init();
if (err) {
LOG_ERR("Modem initialization failed, error: %d", err);
return;
}
/* Fetch Modem IMEI */
LOG_INF("Reading Modem IMEI");
err = nrf_modem_at_cmd(response, sizeof(response), "AT+CGSN=%d", 1);
if (err) {
LOG_ERR("Read Modem IMEI failed, err %d", err);
return;
}else{
LOG_INF("Modem IMEI: %s", response);
}
/* Setup APN for the PDP Context */
LOG_INF("Setting up the APN");
char *apn_stat = "AT%XAPNSTATUS=1,\"" MODEM_APN "\"";
char *at_cgdcont = "AT+CGDCONT=0,\"IPV4V6\",\"" MODEM_APN "\"";
nrf_modem_at_printf(apn_stat); // allow use of APN
err = nrf_modem_at_printf(at_cgdcont); // use conf. APN for PDP context 0 (default LTE bearer)
if (err) {
LOG_ERR("AT+CGDCONT set cmd failed, err %d", err);
return;
}
err = nrf_modem_at_cmd(response, sizeof(response), "AT+CGDCONT?", NULL);
if (err) {
LOG_ERR("APN check failed, err %d", err);
return;
}else{
LOG_INF("AT+CGDCONT?: %s", response);
}
/* Init modem info module */
modem_info_init();
modem_info_params_init(&modem_param);
}
}
/*--------------------------------------------------------------------------*/
static void modem_connect(void)
{
int err;
if (IS_ENABLED(CONFIG_LTE_AUTO_INIT_AND_CONNECT)) {
/* Do nothing, modem is already configured and LTE connected. */
} else {
err = lte_lc_connect_async(lte_handler);
if (err) {
LOG_ERR("Connecting to LTE network failed, error: %d", err);
return;
}
}
}
#endif
/*--------------------------------------------------------------------------*/
static void server_disconnect(void)
{
(void)close(client_fd);
}
/*--------------------------------------------------------------------------*/
static int server_init(void)
{
struct sockaddr_in *server4 = ((struct sockaddr_in *)&host_addr);
server4->sin_family = AF_INET;
server4->sin_port = htons(CONFIG_UDP_SERVER_PORT);
inet_pton(AF_INET, CONFIG_UDP_SERVER_ADDRESS_STATIC,
&server4->sin_addr);
return 0;
}
/*--------------------------------------------------------------------------*/
static int server_connect(void)
{
int err;
client_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (client_fd < 0) {
LOG_ERR("Failed to create UDP socket: %d", errno);
err = -errno;
goto error;
}
err = connect(client_fd, (struct sockaddr *)&host_addr,
sizeof(struct sockaddr_in));
if (err < 0) {
LOG_ERR("Connect failed : %d", errno);
goto error;
}
socket_open = true;
return 0;
error:
server_disconnect();
return err;
}
/*--------------------------------------------------------------------------*/
void main(void)
{
int err;
int ret_0;
LOG_INF("Conexio Stratus < > IoT Creators Demo Application");
printk("Dev kit: %s\n", CONFIG_BOARD);
/* Setup sht4x sensor */
sht = DEVICE_DT_GET_ANY(sensirion_sht4x);
if (!device_is_ready(sht)) {
LOG_ERR("Device %s is not ready.", sht->name);
return;
}
if (!device_is_ready(led_0.port)) {
return;
}
ret_0 = gpio_pin_configure_dt(&led_0, GPIO_OUTPUT_ACTIVE);
if (ret_0 < 0) {
return;
}
/* Turns Blue LED ON */
gpio_pin_set(led_0.port, PIN, 1);
k_msleep(1000);
/* Turns Blue LED OFF */
gpio_pin_set(led_0.port, PIN, 0);
/* Init routines */
work_init();
/* Initialize the modem before calling configure_low_power(). This is
* because the enabling of RAI is dependent on the
* configured network mode which is set during modem initialization.
*/
modem_init();
err = configure_low_power();
if (err) {
LOG_ERR("Unable to set low power configuration, error: %d",err);
}
modem_connect();
k_sem_take(<e_connected, K_FOREVER);
k_msleep(500);
/* Init UDP Socket */
err = server_init();
if (err) {
LOG_ERR("Not able to initialize UDP server connection");
return;
}
/* Connect to UDP Socket */
err = server_connect();
if (err) {
LOG_ERR("Not able to connect to UDP server");
return;
}
/* Perform initial data transmission & schedule periodic tasks */
initial_data_transmission();
k_work_schedule(&data_fetch_work, K_NO_WAIT);
k_work_schedule(&server_transmission_work, K_SECONDS(5));
k_work_schedule(&poll_data_work, K_SECONDS(3));
}
/*--------------------------------------------------------------------------*/
Compiling and Uploading the firmware to the Stratus DeviceTo compile the application, open a terminal window in the application directory and issue the following west command:
west build -b conexio_stratus_ns
Once the application is compiled successfully, connect the Stratus device to the USB port and put it into DFU mode.
Flash the compiled firmware using newtmgr:
newtmgr -c serial image upload build/zephyr/app_update.bin
Open up a serial console and reset the Stratus device. The following serial UART output will be displayed in the terminal.
23-01-31 20:38:51 --> *** Booting Zephyr OS build v3.1.99-ncs1 ***
2023-01-31 20:38:51 --> I: Conexio Stratus < > IoT Creators Demo Application
2023-01-31 20:38:51 --> Dev kit: conexio_stratus
2023-01-31 20:38:52 --> I: Reading Modem IMEI
2023-01-31 20:38:52 --> I: Modem IMEI: +CGSN: "351516178699401"
2023-01-31 20:38:52 -->
2023-01-31 20:38:52 --> OK
2023-01-31 20:38:52 -->
2023-01-31 20:38:52 -->
2023-01-31 20:38:52 --> I: Setting up the APN
2023-01-31 20:38:52 --> I: AT+CGDCONT?: +CGDCONT: 0,"IPV4V6","scs.telekom.tma.iot","",0,0
2023-01-31 20:38:52 -->
2023-01-31 20:38:52 --> OK
2023-01-31 20:38:52 -->
2023-01-31 20:38:52 -->
2023-01-31 20:39:05 --> +CEREG: 2,"412D","03392510",7
2023-01-31 20:39:05 --> I: LTE cell changed: Cell ID: 54076688, Tracking area: 16685
2023-01-31 20:39:05 --> +CSCON: 1
2023-01-31 20:39:05 --> I: RRC mode: Connected
2023-01-31 20:39:05 --> +CEREG: 2,"412D","03392510",7,0,11
2023-01-31 20:39:06 --> +CSCON: 0
2023-01-31 20:39:06 --> I: RRC mode: Idle
2023-01-31 20:39:08 --> +CEREG: 2,"A2EB","0051F115",7
2023-01-31 20:39:08 --> I: LTE cell changed: Cell ID: 5370133, Tracking area: 41707
2023-01-31 20:39:08 --> +CSCON: 1
2023-01-31 20:39:08 --> I: RRC mode: Connected
2023-01-31 20:39:13 --> +CEREG: 5,"A2EB","0051F115",7,,,"11100000","11100000"
2023-01-31 20:39:13 --> I: Network registration status: Connected - roaming
2023-01-31 20:39:13 --> I: PSM parameter update: TAU: 3240, Active time: -1
2023-01-31 20:39:13 --> +CEDRXP: 4,"1001","1001","0001"
2023-01-31 20:39:13 --> I: eDRX parameter update: eDRX: 163.839996, PTW: 2.560000
2023-01-31 20:39:13 --> I: Transmitting initial UDP data
2023-01-31 20:39:13 --> I: Sending UDP payload, length: 87, data: {"Msg":"Event: Stratus connected, 351516178699401, 310260, 12","Oper":"310260","Bd":12}
2023-01-31 20:39:18 --> I: Transmitting sensor data to IoTCreators cloud
2023-01-31 20:39:18 --> I: Sending UDP payload, length: 36, data: {"Temp":24.674219,"Humid":12.413818}
Next, after the device is connected to the network, we need to register our device to the IoTCreators platform here. Learn how to do so by going over the documentation provided by the IoTCreators here.
Simply register the device by creating an account and entering the project page of your starter kit.
Click "REGISTER DEVICE" and the following window will pop up.
Paste or enter your device's IMEI number that is displayed on the terminal window after the device bootup. Next, select which communication protocol you'd like to use for sending and retrieving data with your device; here we will choose UDP (User Datagram Protocol) as our communication protocol. Click "REGISTER DEVICE" to complete the registration process. You should now see your device in the project window.
Let's Test it Out 🔌Assuming we have the Stratus kit plugged in and it is successfully connected to the cellular network, we're ready to test the end-to-end bidirectional communication with the IoTCreators platform.
Simply, hit reset on the Stratus kit to restart the data communication. On the projects page, you will notice the "Last message" and the "Payload" fields being updated.
Hooray! 👍, your device is now successfully offloading Stratus data to the IoTCreators platform. To inspect and decode the data, click on the payload and the following window will appear:
Here, you will see the data fields and the values sent by the Stratus device, which will be updated every 60 seconds as configured in the prj.conf file of the project:
CONFIG_UDP_DATA_UPLOAD_FREQUENCY_SECONDS=60
Controlling the Device RemotelySo far we have only tested the uplink functionality of the platform. Next, we are going to explore how to send messages to the device to control its peripherals. For this project, we will remotely control the BLUE LED present on the Stratus kit.
To send the downlink command, click on the "Actions" button, and from the drop-down menu click "Send a downlink message". The following window will pop up which allows users to post downlink messages to the selected device.
In our sample application, the device accepts the command string "led-on" to turn the LED ON and "led-off" to turn the LED OFF over the UDP protocol.
static void udp_evt_handler(char *buf)
{
LOG_INF("Handling UDP data, data: %s", buf);
if (strcmp(buf, "led-on") == 0) {
/* Turns Blue LED ON */
gpio_pin_set(led_0.port, PIN, 1);
}
else if (strcmp(buf, "led-off") == 0){
/* Turns Blue LED OFF */
gpio_pin_set(led_0.port, PIN, 0);
}
else
LOG_ERR("Received invalid command/string");
}
Simply, to trigger the LED ON, we will type led-on on the "MESSAGE" field as shown below and hit "SEND".
Next time, when the device checks the downlink message which is configured to be 60 seconds:
CONFIG_UDP_DATA_DOWNLOAD_FREQUENCY_SECONDS=60
it will decode the received packet and then turn the BLUE LED on.
CONGRATULATIONS!! 🎉 You just sent your first downlink message from the cloud to your device. This way one can control either lights or power plugs remotely using the downlink commands over the cellular network.
Likewise, to turn OFF the BLUE LED, we will send a led-off command.
That's all for now...
Great Success! 🎉I hope you've seen how easy it can be to set up and configure a low-cost, globally-functional, cellular-enabled IoT device with the Conexio Stratus kit.
Want to build one yourself?
All you need to get started is:
Thank you and happy coding. 🙏
Comments
Please log in or sign up to comment.