samples: net: cloud: Introduce TagoIO IoT cloud http post

Introduce TagoIO IoT Cloud HTTP post client example.  This explorer
Zephyr network resources to demonstrate an end to end application.
The TagoIO allows that any user can test on a easy way Ethernet, WIFI
and Modem (PPP) with BSD sockets.  The example provides overlays to
configure WIFI and Modem.

The application consists an a pseudo temperature sensor that sends
periodically data to TagoIO IoT Cloud platform.  The data can be
visualized on a web browser dashboard, cellphone or tablet.  The
steps to configure TagoIO are described on the example documentation.

Special Variables:
 - CONFIG_TAGOIO_DEVICE_TOKEN   DEVID  token generated by TagoIO
 - CONFIG_TAGOIO_HTTP_WIFI_SSID SSID   when using WIFI
 - CONFIG_TAGOIO_HTTP_WIFI_PSK  PASSWD when using WIFI
 - CONFIG_MODEM_GSM_UART_NAME   UART   label when using MODEM
 - CONFIG_MODEM_GSM_APN         APN    when using MODEM

Signed-off-by: Gerson Fernando Budke <nandojve@gmail.com>
This commit is contained in:
Gerson Fernando Budke 2020-06-19 14:34:49 -03:00 committed by Jukka Rissanen
parent 2bab204ba3
commit 0d216924d5
18 changed files with 661 additions and 0 deletions

View file

@ -448,6 +448,7 @@
/samples/drivers/counter/maxim_ds3231/ @pabigot
/samples/lorawan/ @Mani-Sadhasivam
/samples/net/ @jukkar @tbursztyka @pfalcon
/samples/net/cloud/tagoio_http_post/ @nandojve
/samples/net/dns_resolve/ @jukkar @tbursztyka @pfalcon
/samples/net/lwm2m_client/ @rlubos
/samples/net/mqtt_publisher/ @jukkar @tbursztyka

View file

@ -0,0 +1,10 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.13.1)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(tagoio_http_post)
target_sources(app PRIVATE src/main.c)
target_sources(app PRIVATE src/sockets.c)
target_sources(app PRIVATE src/pinmux.c)
target_sources_ifdef(CONFIG_WIFI app PRIVATE src/wifi.c)

View file

@ -0,0 +1,49 @@
# Copyright (c) 2020 Gerson Fernando Budke <nandojve@gmail.com>
# SPDX-License-Identifier: Apache-2.0
mainmenu "TagoIO IoT Cloud Platform (HTTP Post) Client Configuration"
menu "TagoIO IoT Cloud Configuration"
config TAGOIO_MANUAL_SERVER
bool "Disable DNS lookup"
config TAGOIO_API_SERVER_IP
string "TagoIO Main Server"
depends on TAGOIO_MANUAL_SERVER
default "75.2.65.153"
config TAGOIO_DEVICE_TOKEN
string "The Device Identification Token"
default "device_token"
config TAGOIO_HTTP_PUSH_INTERVAL
int "Configure data push interval in seconds"
range 1 512
default 10
config TAGOIO_HTTP_CONN_TIMEOUT
int "Configure http request timeout in seconds"
range 1 120
default 10
if WIFI
config TAGOIO_HTTP_WIFI_SSID
string "WIFI SSID - Network name"
default "my_network"
config TAGOIO_HTTP_WIFI_PSK
string "WIFI PSK - Network password key"
default "secret_passwd"
endif # WIFI
module = TAGOIO_HTTP_POST
module-str = TagoIO IoT Cloud Platform
module-help = Enables logging for TagoIO IoT Cloud Platform.
source "subsys/logging/Kconfig.template.log_config"
endmenu
source "Kconfig.zephyr"

View file

@ -0,0 +1,161 @@
.. _cloud-tagoio-http-post-sample:
Socket TagoIO HTTP Client
#########################
Overview
********
This sample application implements an HTTP client that will do an HTTP post
request to `TagoIO`_ IoT Service Platform. The sample sends random temperature
values to simulate a real device. This can be used to speed-up development
and shows how to send simple JSON data to `TagoIO`_ servers.
The source code for this sample application can be found at:
:zephyr_file:`samples/net/cloud/tagoio_http_post`.
Requirements
************
- A free `TagoIO`_ account
- A board with internet connectivity, see :ref:`networking`
- The example provides three ways to get internet:
* Ethernet: Using default configuration
* WiFi: Using default configuration plus wifi overlay
* Modem: Using default configuration plus modem overlay
TagoIO Device Configuration
***************************
If you don't have a `TagoIO`_ account, simple create a free account at
`TagoIO`_. After that, add a device selecting Custom HTTP(S) protocol. That
is it! Now reveal your device token. The token will be used to identify your
device when sending data. You need fill ``CONFIG_TAGOIO_DEVICE_TOKEN`` at
:zephyr_file:`samples/net/cloud/tagoio_http_post/prj.conf` file with that
information.
Building and Running
********************
Ethernet
========
You can use this application on a supported board with ethernet port. There
are many like :ref:`sam4e_xpro`, :ref:`sam_v71_xplained_ultra`,
:ref:`frdm_k64f`, :ref:`nucleo_f767zi_board` etc. Pick one and just build
tagoio-http-client sample application with minimal configuration:
.. zephyr-app-commands::
:zephyr-app: samples/net/cloud/tagoio_http_post
:board: [sam4e_xpro | sam_v71_xult | frdm_k64f | nucleo_f767zi]
:goals: build flash
:compact:
WIFI
====
To enable WIFI support, you need a board with an embedded WIFI support like
:ref:`disco_l475_iot1_board` or you can add a shield like
:ref:`module_esp_8266` or :ref:`inventek_eswifi_shield`. Additionally you
need fill ``CONFIG_TAGOIO_HTTP_WIFI_SSID`` with your wifi network SSID and
``CONFIG_TAGOIO_HTTP_WIFI_PSK`` with the correspondent password at
:zephyr_file:`samples/net/cloud/tagoio_http_post/overlay-wifi.conf` file.
.. zephyr-app-commands::
:zephyr-app: samples/net/cloud/tagoio_http_post
:board: disco_l475_iot1
:gen-args: -DOVERLAY_CONFIG=overlay-wifi.conf
:goals: build flash
:compact:
.. zephyr-app-commands::
:zephyr-app: samples/net/cloud/tagoio_http_post
:board: [sam_v71_xult | frdm_k64f | nucleo_f767zi]
:shield: [esp_8266_arduino | inventek_eswifi_arduino_uart]
:gen-args: -DOVERLAY_CONFIG=overlay-wifi.conf
:goals: build flash
:compact:
Modem
=====
The Modem support is quite similar to WIFI, you need a board with an embedded
Modem support or you can add a shield. Currently, the overlay was created to
allow modems with PPP support. This was tested using ``SIMcom SIM808 EVB``.
Additionally you need fill ``CONFIG_MODEM_GSM_UART_NAME`` with the UART label
``CONFIG_MODEM_GSM_APN`` with the correspondent Access Point Name (APN) at
:zephyr_file:`samples/net/cloud/tagoio_http_post/overlay-modem.conf` file.
.. zephyr-app-commands::
:zephyr-app: samples/net/cloud/tagoio_http_post
:board: [sam4e_xpro | frdm_k64f]
:gen-args: -DOVERLAY_CONFIG=overlay-modem.conf
:goals: build flash
:compact:
In a terminal window you can check if communication is happen:
.. code-block:: console
$ minicom -D /dev/ttyACM0
*** Booting Zephyr OS build zephyr-v2.4.0-1135-g137732e23c1e ***
[00:00:02.172,000] <inf> modem_gsm: Manufacturer: SIMCOM_Lt
[00:00:02.227,000] <inf> modem_gsm: Model: SIMCOM_SIM808
[00:00:02.283,000] <inf> modem_gsm: Revision: 1418B04SIM808M32
[00:00:02.338,000] <inf> modem_gsm: IMSI: XXXXXX
[00:00:02.393,000] <inf> modem_gsm: ICCID: XXXXXX
[00:00:02.453,000] <inf> modem_gsm: IMEI: XXXXXX
[00:00:02.574,000] <inf> modem_gsm: Attached to packet service!
[00:00:02.575,000] <inf> net_ppp: Initializing PPP to use UART_3
[00:00:13.370,000] <inf> tagoio: TagoIO IoT - HTTP Client - Temperature demo
[00:00:13.370,000] <inf> tagoio: Temp: 20
[00:00:25.237,000] <inf> tagoio: Temp: 76
[00:00:37.581,000] <inf> tagoio: Temp: 36
[00:00:50.437,000] <inf> tagoio: Temp: 98
Visualizing TagoIO dashboard
****************************
After you got some logs on console it is time to create a dashboard on the
TagoIO to visualize the data.
* Go to the TagoIO web console
* Create a dashboard as Normal, give it a denomination and move next
* Add a line plot graph. You will see your device, temperature variable will
be automatically selected for you.
* Just Save and enjoy!
.. image:: img/TagoIO-pc.jpeg
:width: 640px
:align: center
:alt: TagoIO web dashboard
You can experiment the TagoIO mobile application on your cellphone or tablet.
Simple go to your app store and search by TagoIO, install, sign in, enjoy!
.. image:: img/TagoIO-mobile.jpeg
:width: 480px
:align: center
:alt: TagoIO mobile dashboard
More information at `TagoIO`_ and `TagoIO Documentation`_.
References
**********
.. target-notes::
.. _TagoIO:
https://tago.io/
.. _TagoIO Documentation:
https://docs.tago.io

View file

@ -0,0 +1,2 @@
CONFIG_PINMUX=y
CONFIG_USART_SAM=y

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

View file

@ -0,0 +1,24 @@
# UART support
CONFIG_SERIAL=y
# GSM modem support
CONFIG_MODEM=y
CONFIG_MODEM_SHELL=y
CONFIG_MODEM_CMD_HANDLER_MAX_PARAM_COUNT=20
CONFIG_MODEM_GSM_PPP=y
CONFIG_MODEM_GSM_RX_STACK_SIZE=1024
CONFIG_MODEM_GSM_UART_NAME="<your board UART port label>"
CONFIG_MODEM_GSM_APN="<your Access Point Name (APN)>"
# Network management events
CONFIG_NET_CONNECTION_MANAGER=y
# PPP networking support
CONFIG_NET_NATIVE=y
CONFIG_NET_PPP=y
CONFIG_NET_L2_PPP=y
CONFIG_NET_L2_PPP_TIMEOUT=10000
CONFIG_DNS_RESOLVER_MAX_SERVERS=1
CONFIG_DNS_SERVER_IP_ADDRESSES=y
CONFIG_DNS_SERVER1="8.8.8.8"

View file

@ -0,0 +1,8 @@
CONFIG_WIFI=y
# WiFi offload drivers don't resolve DNS, so use manual IP
CONFIG_TAGOIO_MANUAL_SERVER=y
CONFIG_TAGOIO_HTTP_WIFI_SSID="<your network ssid>"
CONFIG_TAGOIO_HTTP_WIFI_PSK="<yout network passwd>"
CONFIG_NET_CONFIG_SETTINGS=n
CONFIG_NET_DHCPV4=n

View file

@ -0,0 +1,43 @@
# App stack
CONFIG_MAIN_STACK_SIZE=4096
# C Library
# Default use minimal libc and with MINIMAL_LIBC_MALLOC_ARENA_SIZE defining
# HEAP size (512 bytes) are enought to run DNS.
CONFIG_MINIMAL_LIBC_MALLOC_ARENA_SIZE=4096
# Networking config
CONFIG_NETWORKING=y
CONFIG_NET_IPV4=y
CONFIG_NET_IPV6=n
CONFIG_NET_TCP=y
CONFIG_NET_SHELL=y
# Sockets
CONFIG_NET_SOCKETS=y
CONFIG_NET_SOCKETS_POSIX_NAMES=y
CONFIG_NET_SOCKETS_POLL_MAX=4
# Network driver config
CONFIG_TEST_RANDOM_GENERATOR=y
# Network address config
CONFIG_NET_CONFIG_SETTINGS=y
CONFIG_NET_CONFIG_NEED_IPV4=y
CONFIG_NET_DHCPV4=y
# Resolver
CONFIG_DNS_RESOLVER=y
# HTTP
CONFIG_HTTP_CLIENT=y
# Network debug config
CONFIG_LOG=y
CONFIG_LOG_STRDUP_BUF_COUNT=20
CONFIG_NET_LOG=y
CONFIG_NET_SOCKETS_LOG_LEVEL_DBG=n
CONFIG_NET_HTTP_LOG_LEVEL_DBG=n
# TagoIO API
CONFIG_TAGOIO_DEVICE_TOKEN="<your device token API>"

View file

@ -0,0 +1,18 @@
common:
tags: net cloud dns http ppp modem gsm wifi
min_ram: 64
sample:
description: TagoIO HTTP(S) client sample
name: tagoio_http_post
tests:
sample.net.cloud.tagoio_http_post:
platform_allow: frdm_k64f nucleo_f767zi
sample.net.cloud.tagoio_http_post.wifi:
extra_args: OVERLAY_CONFIG="overlay-wifi.conf"
platform_allow: disco_l475_iot1
sample.net.cloud.tagoio_http_post.wifi.esp:
extra_args: SHIELD=esp_8266_arduino OVERLAY_CONFIG="overlay-wifi.conf"
platform_allow: frdm_k64f nucleo_f767zi
sample.net.cloud.tagoio_http_post.modem:
extra_args: OVERLAY_CONFIG="overlay-modem.conf"
platform_allow: sam4e_xpro frdm_k64f

View file

@ -0,0 +1,86 @@
/*
* Copyright (c) 2020 Gerson Fernando Budke <nandojve@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <logging/log.h>
LOG_MODULE_REGISTER(tagoio_http_post, CONFIG_TAGOIO_HTTP_POST_LOG_LEVEL);
#include <zephyr.h>
#include <net/socket.h>
#include <net/http_client.h>
#include <random/rand32.h>
#include <stdio.h>
#include "wifi.h"
#include "sockets.h"
static struct tagoio_context ctx;
static void response_cb(struct http_response *rsp,
enum http_final_call final_data,
void *user_data)
{
if (final_data == HTTP_DATA_MORE) {
LOG_DBG("Partial data received (%zd bytes)", rsp->data_len);
} else if (final_data == HTTP_DATA_FINAL) {
LOG_DBG("All the data received (%zd bytes)", rsp->data_len);
}
LOG_DBG("Response status %s", rsp->http_status);
}
static int collect_data(void)
{
#define lower 20000
#define upper 100000
#define base 1000.00f
float temp;
/* Generate a temperature between 20 and 100 celsius degree */
temp = ((sys_rand32_get() % (upper - lower + 1)) + lower);
temp /= base;
(void)snprintf(ctx.payload, sizeof(ctx.payload),
"{\"variable\": \"temperature\","
"\"unit\": \"c\",\"value\": %f}",
temp);
/* LOG doesn't print float #18351 */
LOG_INF("Temp: %d", (int) temp);
return 0;
}
static void next_turn(void)
{
if (collect_data() < 0) {
LOG_INF("Error collecting data.");
return;
}
if (tagoio_connect(&ctx) < 0) {
LOG_INF("No connection available.");
return;
}
if (tagoio_http_push(&ctx, response_cb) < 0) {
LOG_INF("Error pushing data.");
return;
}
}
void main(void)
{
LOG_INF("TagoIO IoT - HTTP Client - Temperature demo");
wifi_connect();
while (true) {
next_turn();
k_sleep(K_SECONDS(CONFIG_TAGOIO_HTTP_PUSH_INTERVAL));
}
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2020 Gerson Fernando Budke <nandojve@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <init.h>
#include <drivers/pinmux.h>
#if defined(CONFIG_BOARD_FRDM_K64F)
#include <fsl_port.h>
#endif
static int tagoio_pinmux_init(const struct device *dev)
{
ARG_UNUSED(dev);
#if defined(CONFIG_BOARD_FRDM_K64F)
const struct device *portc =
device_get_binding(CONFIG_PINMUX_MCUX_PORTC_NAME);
pinmux_pin_set(portc, 2, PORT_PCR_MUX(kPORT_MuxAsGpio));
pinmux_pin_set(portc, 3, PORT_PCR_MUX(kPORT_MuxAsGpio));
#endif
return 0;
}
SYS_INIT(tagoio_pinmux_init, POST_KERNEL, CONFIG_PINMUX_INIT_PRIORITY);

View file

@ -0,0 +1,128 @@
/*
* Copyright (c) 2020 Gerson Fernando Budke <nandojve@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <logging/log.h>
LOG_MODULE_DECLARE(tagoio_http_post, CONFIG_TAGOIO_HTTP_POST_LOG_LEVEL);
#include <net/net_ip.h>
#include <net/socket.h>
#include <net/socketutils.h>
#include <net/dns_resolve.h>
#include <net/tls_credentials.h>
#include <net/http_client.h>
#include "sockets.h"
#if IS_ENABLED(CONFIG_TAGOIO_MANUAL_SERVER)
#define TAGOIO_SERVER CONFIG_TAGOIO_API_SERVER_IP
#else
#define TAGOIO_SERVER "api.tago.io"
#endif
#define TAGOIO_URL "/data"
#define HTTP_PORT "80"
static const char *tagoio_http_headers[] = {
"Device-Token: " CONFIG_TAGOIO_DEVICE_TOKEN "\r\n",
"Content-Type: application/json\r\n",
"_ssl: false\r\n",
NULL
};
int tagoio_connect(struct tagoio_context *ctx)
{
struct addrinfo *addr;
struct addrinfo hints;
char hr_addr[INET6_ADDRSTRLEN];
char *port;
int dns_attempts = 3;
int ret = -1;
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
if (IS_ENABLED(CONFIG_NET_IPV6)) {
hints.ai_family = AF_INET6;
} else if (IS_ENABLED(CONFIG_NET_IPV4)) {
hints.ai_family = AF_INET;
}
port = HTTP_PORT;
while (dns_attempts--) {
ret = getaddrinfo(TAGOIO_SERVER, port, &hints, &addr);
if (ret == 0) {
break;
}
k_sleep(K_SECONDS(1));
}
if (ret < 0) {
LOG_ERR("Could not resolve dns, error: %d", ret);
return ret;
}
LOG_DBG("%s address: %s",
(addr->ai_family == AF_INET ? "IPv4" : "IPv6"),
log_strdup(net_addr_ntop(addr->ai_family,
&net_sin(addr->ai_addr)->sin_addr,
hr_addr, sizeof(hr_addr))));
ctx->sock = socket(hints.ai_family,
hints.ai_socktype,
hints.ai_protocol);
if (ctx->sock < 0) {
LOG_ERR("Failed to create %s HTTP socket (%d)",
(addr->ai_family == AF_INET ? "IPv4" : "IPv6"),
-errno);
freeaddrinfo(addr);
return -errno;
}
if (connect(ctx->sock, addr->ai_addr, addr->ai_addrlen) < 0) {
LOG_ERR("Cannot connect to %s remote (%d)",
(addr->ai_family == AF_INET ? "IPv4" : "IPv6"),
-errno);
freeaddrinfo(addr);
return -errno;
}
freeaddrinfo(addr);
return 0;
}
int tagoio_http_push(struct tagoio_context *ctx,
http_response_cb_t resp_cb)
{
struct http_request req;
int ret;
memset(&req, 0, sizeof(req));
req.method = HTTP_POST;
req.host = TAGOIO_SERVER;
req.port = HTTP_PORT;
req.url = TAGOIO_URL;
req.header_fields = tagoio_http_headers;
req.protocol = "HTTP/1.1";
req.response = resp_cb;
req.payload = ctx->payload;
req.payload_len = strlen(ctx->payload);
req.recv_buf = ctx->resp;
req.recv_buf_len = sizeof(ctx->resp);
ret = http_client_req(ctx->sock, &req,
CONFIG_TAGOIO_HTTP_CONN_TIMEOUT * MSEC_PER_SEC,
ctx);
close(ctx->sock);
ctx->sock = -1;
return ret;
}

View file

@ -0,0 +1,17 @@
/*
* Copyright (c) 2020 Gerson Fernando Budke <nandojve@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#define TAGOIO_SOCKET_MAX_BUF_LEN 1280
struct tagoio_context {
int sock;
uint8_t payload[TAGOIO_SOCKET_MAX_BUF_LEN];
uint8_t resp[TAGOIO_SOCKET_MAX_BUF_LEN];
};
int tagoio_connect(struct tagoio_context *ctx);
int tagoio_http_push(struct tagoio_context *ctx,
http_response_cb_t resp_cb);

View file

@ -0,0 +1,73 @@
/*
* Copyright (c) 2020 Gerson Fernando Budke <nandojve@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <logging/log.h>
LOG_MODULE_DECLARE(tagoio_http_post, CONFIG_TAGOIO_HTTP_POST_LOG_LEVEL);
#include <net/wifi_mgmt.h>
static int connected;
static struct net_mgmt_event_callback wifi_shell_mgmt_cb;
static void handle_wifi_connect_result(struct net_mgmt_event_callback *cb)
{
const struct wifi_status *status = (const struct wifi_status *)
cb->info;
if (status->status) {
LOG_ERR("Connection request failed (%d)", status->status);
} else {
LOG_INF("WIFI Connected");
connected = 1;
}
}
static void wifi_mgmt_event_handler(struct net_mgmt_event_callback *cb,
uint32_t mgmt_event, struct net_if *iface)
{
switch (mgmt_event) {
case NET_EVENT_WIFI_CONNECT_RESULT:
handle_wifi_connect_result(cb);
break;
default:
break;
}
}
void wifi_connect(void)
{
net_mgmt_init_event_callback(&wifi_shell_mgmt_cb,
wifi_mgmt_event_handler,
NET_EVENT_WIFI_CONNECT_RESULT);
net_mgmt_add_event_callback(&wifi_shell_mgmt_cb);
struct net_if *iface = net_if_get_default();
static struct wifi_connect_req_params cnx_params = {
.ssid = CONFIG_TAGOIO_HTTP_WIFI_SSID,
.ssid_length = 0,
.psk = CONFIG_TAGOIO_HTTP_WIFI_PSK,
.psk_length = 0,
.channel = 0,
.security = WIFI_SECURITY_TYPE_PSK,
};
cnx_params.ssid_length = strlen(CONFIG_TAGOIO_HTTP_WIFI_SSID);
cnx_params.psk_length = strlen(CONFIG_TAGOIO_HTTP_WIFI_PSK);
connected = 0;
LOG_INF("WIFI try connecting to %s...", CONFIG_TAGOIO_HTTP_WIFI_SSID);
if (net_mgmt(NET_REQUEST_WIFI_CONNECT, iface,
&cnx_params, sizeof(struct wifi_connect_req_params))) {
return;
}
while (connected == 0) {
k_msleep(100);
};
}

View file

@ -0,0 +1,11 @@
/*
* Copyright (c) 2020 Gerson Fernando Budke <nandojve@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#if defined(CONFIG_WIFI)
void wifi_connect(void);
#else
#define wifi_connect()
#endif

View file

@ -542,6 +542,7 @@ UNDEF_KCONFIG_WHITELIST = {
"SRAM2", # Referenced in a comment in samples/application_development
"STACK_SIZE", # Used as an example in the Kconfig docs
"STD_CPP", # Referenced in CMake comment
"TAGOIO_HTTP_POST_LOG_LEVEL", # Used as in samples/net/cloud/tagoio
"TEST1",
"TYPE_BOOLEAN",
"USB_CONSOLE",