630|0

272

帖子

3

TA的资源

纯净的硅(初级)

楼主
 

【nRF7002-DK Wi-Fi® 6开发套件评测】基于Mbed-TLS请求高德天气API的HTTPS接口 [复制链接]

在 【nRF7002-DK Wi-Fi® 6开发套件评测】WiFi联网请求JSON数据控制LED  中,分享了在nRF7002-DK上通过WIFi联网来获取HTTP网址的数据,这一篇分享,则是请求高德天气的API接口,是HTTPS的。

 

一、前言

要在 nRF7002-DK上的Zephyr 中发起HTTPS请求,需要使用到 Mbed-TLS 。

在常见的电脑操作系统,如Linux、Windows、macOS中,对于SSL协议的支持,一般使用的是OpenSSL。

而在嵌入式设备上,通常使用的是Mbed-TLS,它是专门设计为设计为适合小型嵌入式设备的。

Zephyr提供了mbedtls模块,可以在程序中,很方便的加载Mbed-TLS,来实现基于SSL/TLS的安全数据通讯。

 

二、高德天气接口

在nRF7002-DK上测试过多中天气API接口,最终确定比较好用的,是高德天气接口。

要使用高德天气接口,可以访问: https://lbs.amap.com/api/webservice/guide/api/weatherinfo/ ,了解该接口的具体调用方式:

 

 

在该页面上,需要先注册登录,然后获取API Key:

 

获取到API Key以后,可以直接通过网址检查是否可以正确使用,具体的调用网址如下:

https://restapi.amap.com/v3/weather/weatherInfo?key=【你的API_KEY】&city=110000&extensions=base

在上述网址中,请使用你自己申请的API Key,并修改city为你想要查询的城市编码。

具体的城市编码,可以在 https://lbs.amap.com/api/webservice/download 了解详情。

 

上述网址请求的是北京的天气信息,具体结果如下:

 

 

三、Root证书获取

要在 nRF7002-DK 上的 Zephyr中发起HTTPS请求,还需要预先获取https网址所对应的Root证书,否则是无法正确发起请求的。

要获取Root证书,按照如下的步骤进行:

1. 访问实际要访问的API的HTTPS网址,并在浏览器中,查看证书:

 

 

  

 

 

通过上面的步骤,导出了 GlobalSignRootCA.cer 证书。注意导出的时候,证书文件名不要有空格。

 

然后,使用下面的命令,转换证书:

openssl x509 -in GlobalSignRootCA.cer -outform PEM -out GlobalSignRootCA.pem
openssl x509 -outform der -in GlobalSignRootCA.pem -out GlobalSignRootCA.der

 

最终得到实际需要的证书文件:GlobalSignRootCA.der

 

 

这个证书的获取,是非常关键的,否则在后续发起HTTPS请求的时候,必然会出错。

另外,有一些HTTPS网址的Root证书,即使获取了,也不一定能够使用,这一点务必注意。高德天气的接口,我反复测试过,确保正确。

 

四、在nRF7002-DK上的Zephyr 中发起HTTPS请求

这次的演示程序,在  【nRF7002-DK Wi-Fi® 6开发套件评测】WiFi联网请求JSON数据控制LED   的基础上,参考官方演示,添加了HTTPS的部分。

具体要处理的步骤如下:

1. 修改prj.conf,添加MBEDTLS相关的配置:

CONFIG_MBEDTLS=y
CONFIG_MBEDTLS_BUILTIN=y
CONFIG_MBEDTLS_ENABLE_HEAP=y
CONFIG_MBEDTLS_HEAP_SIZE=40000
CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=7168

CONFIG_MBEDTLS_PEM_CERTIFICATE_FORMAT=y
CONFIG_MBEDTLS_DEBUG_LEVEL=4

# mbed TLS and security
CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384
CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=16384
CONFIG_MBEDTLS_HEAP_SIZE=128000
CONFIG_MBEDTLS_TLS_LIBRARY=y
CONFIG_MBEDTLS_X509_LIBRARY=y
CONFIG_MBEDTLS_PKCS1_V15=y
CONFIG_NRF_SECURITY_ADVANCED=y
CONFIG_NORDIC_SECURITY_BACKEND=y
CONFIG_PSA_WANT_ALG_SHA_1=y
CONFIG_PSA_WANT_ALG_RSA_PKCS1V15_CRYPT=y
CONFIG_PSA_WANT_ALG_RSA_PKCS1V15_SIGN=y
CONFIG_MBEDTLS_SSL_DEBUG_ALL=y
CONFIG_MBEDTLS_LOG_LEVEL_DBG=y
CONFIG_MBEDTLS_SSL_RENEGOTIATION=y
CONFIG_MBEDTLS_SSL_SERVER_NAME_INDICATION=y
CONFIG_MBEDTLS_KEY_EXCHANGE_ALL_ENABLED=y
CONFIG_MBEDTLS_ECP_ALL_ENABLED=y
CONFIG_MBEDTLS_CIPHER_ALL_ENABLED=y
CONFIG_MBEDTLS_SSL_SESSION_TICKETS=y
CONFIG_MBEDTLS_SSL_CACHE_C=y
CONFIG_MBEDTLS_SSL_TICKET_C=y
# CONFIG_PSA_WANT_ECC_TWISTED_EDWARDS_255=y
# CONFIG_PSA_CRYPTO_DRIVER_ECC_TWISTED_EDWARDS_255_OBERON=y
CONFIG_MBEDTLS_RSA_C=y

CONFIG_NET_SOCKETS_SOCKOPT_TLS=y

上述配置,应该是可以优化的,感兴趣的话,可以尝试去掉多余的配置进行编译测试,例如:

# TLS configuration
CONFIG_MBEDTLS=y
CONFIG_MBEDTLS_BUILTIN=y
CONFIG_MBEDTLS_ENABLE_HEAP=y
CONFIG_MBEDTLS_HEAP_SIZE=40000
CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=7168

CONFIG_NET_SOCKETS_SOCKOPT_TLS=y

 

2. 修改CMakeLists.txt,添加证书处理:

include(${ZEPHYR_BASE}/samples/net/common/common.cmake)

set(gen_dir ${ZEPHYR_BINARY_DIR}/include/generated/)

generate_inc_file_for_target(
    app
    src/GlobalSignRootCA.der
    ${gen_dir}/GlobalSignRootCA.der.inc
)

注意上述配置中,在Root证书获取部分获取的证书,保存到了 src/GlobalSignRootCA.der ,以便编译过程中进行预处理。

 

3. 证书加载头文件:

要把CMake预处理的Root证书加载到代码中来,需要添加 src/ca_certificate.h 文件,内容如下:

#ifndef __CA_CERTIFICATE_H__
#define __CA_CERTIFICATE_H__

#define CA_CERTIFICATE_TAG 1

static const unsigned char ca_certificate[] = {
#include "GlobalSignRootCA.der.inc"
};

#endif /* __CA_CERTIFICATE_H__ */

上述代码中的 GlobalSignRootCA.der.inc ,就是在 CMakeLists.txt 预处理的结果。

 

4. main.c中添加对应的调用

完整的main.c代码如下:

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(wifi_weather, CONFIG_LOG_DEFAULT_LEVEL);

#include <nrfx_clock.h>
#include <zephyr/kernel.h>
#include <stdio.h>
#include <stdlib.h>
#include <zephyr/shell/shell.h>
#include <zephyr/sys/printk.h>
#include <zephyr/init.h>

#include <zephyr/net/net_if.h>
#include <zephyr/net/wifi_mgmt.h>
#include <zephyr/net/net_event.h>
#include <zephyr/drivers/gpio.h>

#include <qspi_if.h>

#include "net_private.h"

// 网络连接
#include <zephyr/net/socket.h>
#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
#include <zephyr/net/tls_credentials.h>
#include "ca_certificate.h"
#endif


// keyname=GlobalSignRootCA
// openssl x509 -in $keyname.cer -outform PEM -out $keyname.pem
// openssl x509 -outform der -in $keyname.pem -out $keyname.der

#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
#define HTTP_HOST "restapi.amap.com"
#define HTTP_PORT "443"
#endif
#define HTTP_PATH "/v3/weather/weatherInfo?key=【你的API_KEY】&city=110000&extensions=base"

#define SSTRLEN(s) (sizeof(s) - 1)
#define CHECK(r) { if (r == -1) { printf("Error: " #r "\n"); exit(1); } }

#define REQUEST "GET " HTTP_PATH " HTTP/1.0\r\nHost: " HTTP_HOST "\r\n\r\n"

static char response[1024];
// 网络连接

// JSON
#include <zephyr/data/json.h>

/*
{
	"status": "1",
	"count": "1",
	"info": "OK",
	"infocode": "10000",
	"lives": [
		{
			"province": "北京",
			"city": "北\ufffd\ufffd\ufffd市",
			"adcode": "110000",
			"weather": "晴",
			"temperature": "-1",
			"winddirection": "东",
			"windpower": "≤3",
			"humidity": "57",
			"reporttime": "2023-12-03 22:10:22",
			"temperature_float": "-1.0",
			"humidity_float": "57.0"
		}
	]
}
*/

struct weather_live {
	const char *province;
	const char *city;
	const char *adcode;
	const char *weather;
	const char *temperature;
	const char *winddirection;
	const char *windpower;
	const char *humidity;
	const char *reporttime;
	const char *temperature_float;
	const char *humidity_float;
};

struct weather {
	const char *status;
	const char *count;
	const char *info;
	const char *infocode;
	struct weather_live lives[1];
	size_t lives_len;
};

static const struct json_obj_descr weather_live_descr[] = {
  JSON_OBJ_DESCR_PRIM(struct weather_live, province, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct weather_live, city, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct weather_live, adcode, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct weather_live, weather, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct weather_live, temperature, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct weather_live, winddirection, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct weather_live, windpower, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct weather_live, humidity, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct weather_live, reporttime, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct weather_live, temperature_float, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct weather_live, humidity_float, JSON_TOK_STRING)
};

static const struct json_obj_descr weather_descr[] = {
	JSON_OBJ_DESCR_PRIM(struct weather, status, JSON_TOK_STRING),
	JSON_OBJ_DESCR_PRIM(struct weather, count, JSON_TOK_STRING),
	JSON_OBJ_DESCR_PRIM(struct weather, info, JSON_TOK_STRING),
	JSON_OBJ_DESCR_PRIM(struct weather, infocode, JSON_TOK_STRING),
	JSON_OBJ_DESCR_OBJ_ARRAY(struct weather, lives, 1, lives_len, weather_live_descr, ARRAY_SIZE(weather_live_descr)),
};
// JSON

#define WIFI_SHELL_MODULE "wifi"

#define WIFI_SHELL_MGMT_EVENTS (NET_EVENT_WIFI_CONNECT_RESULT |		\
				NET_EVENT_WIFI_DISCONNECT_RESULT)

#define MAX_SSID_LEN        32
#define STATUS_POLLING_MS   300

/* 1000 msec = 1 sec */
#define LED_SLEEP_TIME_MS   500

/* The devicetree node identifier for the "led0" alias. */
#define LED0_NODE DT_ALIAS(led0)
/*
 * A build error on this line means your board is unsupported.
 * See the sample documentation for information on how to fix this.
 */
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);

static struct net_mgmt_event_callback wifi_shell_mgmt_cb;
static struct net_mgmt_event_callback net_shell_mgmt_cb;

static struct {
	const struct shell *sh;
	union {
		struct {
			uint8_t connected	: 1;
			uint8_t connect_result	: 1;
			uint8_t disconnect_requested	: 1;
			uint8_t _unused		: 5;
		};
		uint8_t all;
	};
} context;

void toggle_led(void)
{
	int ret;

	if (!device_is_ready(led.port)) {
		LOG_ERR("LED device is not ready");
		return;
	}

	ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
	if (ret < 0) {
		LOG_ERR("Error %d: failed to configure LED pin", ret);
		return;
	}

	while (1) {
		if (context.connected) {
			gpio_pin_toggle_dt(&led);
			k_msleep(LED_SLEEP_TIME_MS);
		} else {
			gpio_pin_set_dt(&led, 0);
			k_msleep(LED_SLEEP_TIME_MS);
		}
	}
}

K_THREAD_DEFINE(led_thread_id, 1024, toggle_led, NULL, NULL, NULL,
		7, 0, 0);

static int cmd_wifi_status(void)
{
	struct net_if *iface = net_if_get_default();
	struct wifi_iface_status status = { 0 };

	if (net_mgmt(NET_REQUEST_WIFI_IFACE_STATUS, iface, &status,
				sizeof(struct wifi_iface_status))) {
		LOG_INF("Status request failed");

		return -ENOEXEC;
	}

	LOG_INF("==================");
	LOG_INF("State: %s", wifi_state_txt(status.state));

	if (status.state >= WIFI_STATE_ASSOCIATED) {
		uint8_t mac_string_buf[sizeof("xx:xx:xx:xx:xx:xx")];

		LOG_INF("Interface Mode: %s",
		       wifi_mode_txt(status.iface_mode));
		LOG_INF("Link Mode: %s",
		       wifi_link_mode_txt(status.link_mode));
		LOG_INF("SSID: %-32s", status.ssid);
		LOG_INF("BSSID: %s",
		       net_sprint_ll_addr_buf(
				status.bssid, WIFI_MAC_ADDR_LEN,
				mac_string_buf, sizeof(mac_string_buf)));
		LOG_INF("Band: %s", wifi_band_txt(status.band));
		LOG_INF("Channel: %d", status.channel);
		LOG_INF("Security: %s", wifi_security_txt(status.security));
		LOG_INF("MFP: %s", wifi_mfp_txt(status.mfp));
		LOG_INF("RSSI: %d", status.rssi);
	}
	return 0;
}

static void handle_wifi_connect_result(struct net_mgmt_event_callback *cb)
{
	const struct wifi_status *status =
		(const struct wifi_status *) cb->info;

	if (context.connected) {
		return;
	}

	if (status->status) {
		LOG_ERR("Connection failed (%d)", status->status);
	} else {
		LOG_INF("Connected");
		context.connected = true;
	}

	context.connect_result = true;
}

static void handle_wifi_disconnect_result(struct net_mgmt_event_callback *cb)
{
	const struct wifi_status *status =
		(const struct wifi_status *) cb->info;

	if (!context.connected) {
		return;
	}

	if (context.disconnect_requested) {
		LOG_INF("Disconnection request %s (%d)",
			 status->status ? "failed" : "done",
					status->status);
		context.disconnect_requested = false;
	} else {
		LOG_INF("Received Disconnected");
		context.connected = false;
	}

	cmd_wifi_status();
}

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;
	case NET_EVENT_WIFI_DISCONNECT_RESULT:
		handle_wifi_disconnect_result(cb);
		break;
	default:
		break;
	}
}

static void print_dhcp_ip(struct net_mgmt_event_callback *cb)
{
	/* Get DHCP info from struct net_if_dhcpv4 and print */
	const struct net_if_dhcpv4 *dhcpv4 = cb->info;
	const struct in_addr *addr = &dhcpv4->requested_ip;
	char dhcp_info[128];

	net_addr_ntop(AF_INET, addr, dhcp_info, sizeof(dhcp_info));

	LOG_INF("DHCP IP address: %s", dhcp_info);
}
static void net_mgmt_event_handler(struct net_mgmt_event_callback *cb,
				    uint32_t mgmt_event, struct net_if *iface)
{
	switch (mgmt_event) {
	case NET_EVENT_IPV4_DHCP_BOUND:
		print_dhcp_ip(cb);
		break;
	default:
		break;
	}
}

static int __wifi_args_to_params(struct wifi_connect_req_params *params)
{

	params->timeout =  CONFIG_STA_CONN_TIMEOUT_SEC * MSEC_PER_SEC;

	if (params->timeout == 0) {
		params->timeout = SYS_FOREVER_MS;
	}

	/* SSID */
	params->ssid = CONFIG_STA_SAMPLE_SSID;
	params->ssid_length = strlen(params->ssid);

#if defined(CONFIG_STA_KEY_MGMT_WPA2)
	params->security = 1;
#elif defined(CONFIG_STA_KEY_MGMT_WPA2_256)
	params->security = 2;
#elif defined(CONFIG_STA_KEY_MGMT_WPA3)
	params->security = 3;
#else
	params->security = 0;
#endif

#if !defined(CONFIG_STA_KEY_MGMT_NONE)
	params->psk = CONFIG_STA_SAMPLE_PASSWORD;
	params->psk_length = strlen(params->psk);
#endif
	params->channel = WIFI_CHANNEL_ANY;

	/* MFP (optional) */
	params->mfp = WIFI_MFP_OPTIONAL;

	return 0;
}

static int wifi_connect(void)
{
	struct net_if *iface = net_if_get_default();
	static struct wifi_connect_req_params cnx_params;

	context.connected = false;
	context.connect_result = false;
	__wifi_args_to_params(&cnx_params);

	if (net_mgmt(NET_REQUEST_WIFI_CONNECT, iface,
		     &cnx_params, sizeof(struct wifi_connect_req_params))) {
		LOG_ERR("Connection request failed");

		return -ENOEXEC;
	}

	LOG_INF("Connection requested");

	return 0;
}

int bytes_from_str(const char *str, uint8_t *bytes, size_t bytes_len)
{
	size_t i;
	char byte_str[3];

	if (strlen(str) != bytes_len * 2) {
		LOG_ERR("Invalid string length: %zu (expected: %d)\n",
			strlen(str), bytes_len * 2);
		return -EINVAL;
	}

	for (i = 0; i < bytes_len; i++) {
		memcpy(byte_str, str + i * 2, 2);
		byte_str[2] = '\0';
		bytes[i] = strtol(byte_str, NULL, 16);
	}

	return 0;
}

int main(void)
{
	memset(&context, 0, sizeof(context));

	net_mgmt_init_event_callback(&wifi_shell_mgmt_cb,
				     wifi_mgmt_event_handler,
				     WIFI_SHELL_MGMT_EVENTS);

	net_mgmt_add_event_callback(&wifi_shell_mgmt_cb);


	net_mgmt_init_event_callback(&net_shell_mgmt_cb,
				     net_mgmt_event_handler,
				     NET_EVENT_IPV4_DHCP_BOUND);

	net_mgmt_add_event_callback(&net_shell_mgmt_cb);

	LOG_INF("Starting %s with CPU frequency: %d MHz", CONFIG_BOARD, SystemCoreClock/MHZ(1));
	k_sleep(K_SECONDS(1));

#if defined(CONFIG_BOARD_NRF7002DK_NRF7001_NRF5340_CPUAPP) || \
	defined(CONFIG_BOARD_NRF7002DK_NRF5340_CPUAPP)
	if (strlen(CONFIG_NRF700X_QSPI_ENCRYPTION_KEY)) {
		char key[QSPI_KEY_LEN_BYTES];
		int ret;

		ret = bytes_from_str(CONFIG_NRF700X_QSPI_ENCRYPTION_KEY, key, sizeof(key));
		if (ret) {
			LOG_ERR("Failed to parse encryption key: %d\n", ret);
			return 0;
		}

		LOG_DBG("QSPI Encryption key: ");
		for (int i = 0; i < QSPI_KEY_LEN_BYTES; i++) {
			LOG_DBG("%02x", key[i]);
		}
		LOG_DBG("\n");

		ret = qspi_enable_encryption(key);
		if (ret) {
			LOG_ERR("Failed to enable encryption: %d\n", ret);
			return 0;
		}
		LOG_INF("QSPI Encryption enabled");
	} else {
		LOG_INF("QSPI Encryption disabled");
	}
#endif /* CONFIG_BOARD_NRF700XDK_NRF5340 */

	LOG_INF("Static IP address (overridable): %s/%s -> %s",
		CONFIG_NET_CONFIG_MY_IPV4_ADDR,
		CONFIG_NET_CONFIG_MY_IPV4_NETMASK,
		CONFIG_NET_CONFIG_MY_IPV4_GW);

	while (1) {
		wifi_connect();

		while (!context.connect_result) {
			cmd_wifi_status();
			k_sleep(K_MSEC(STATUS_POLLING_MS));
		}

		if (context.connected) {
			// k_sleep(K_FOREVER);
			LOG_INF("WiFi Connect OK!");
			break;
		}
	}

	static struct addrinfo hints;
	struct addrinfo *res;
	int st, sock;

#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
	tls_credential_add(CA_CERTIFICATE_TAG, TLS_CREDENTIAL_CA_CERTIFICATE,
			   ca_certificate, sizeof(ca_certificate));
#endif

#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
	printf("Preparing HTTP GET request for https://" HTTP_HOST
	       ":" HTTP_PORT HTTP_PATH "\n");
#else
	printf("Preparing HTTP GET request for http://" HTTP_HOST
	       ":" HTTP_PORT HTTP_PATH "\n");
#endif
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	st = getaddrinfo(HTTP_HOST, HTTP_PORT, &hints, &res);
	printf("getaddrinfo status: %d\n", st);

	if (st != 0) {
		printf("Unable to resolve address, quitting\n");
		return 0;
	}

	while (1) {
		LOG_INF("Main Loop.");

#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
			sock = socket(res->ai_family, res->ai_socktype, IPPROTO_TLS_1_2);
#else
			sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
#endif

		CHECK(sock);
		printf("sock = %d\n", sock);

#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
		sec_tag_t sec_tag_opt[] = {
			CA_CERTIFICATE_TAG,
		};
		CHECK(setsockopt(sock, SOL_TLS, TLS_SEC_TAG_LIST,
				sec_tag_opt, sizeof(sec_tag_opt)));

		CHECK(setsockopt(sock, SOL_TLS, TLS_HOSTNAME,
				HTTP_HOST, sizeof(HTTP_HOST)))
#endif

		int ret = connect(sock, res->ai_addr, res->ai_addrlen);
		printf("errno = %d, ret=%d\n", errno, ret);
		if(ret==-1) {
			printf("exit");
			exit(1);
		}
		// CHECK(connect(sock, res->ai_addr, res->ai_addrlen));
		CHECK(send(sock, REQUEST, SSTRLEN(REQUEST), 0));

		// printf("Response:\n\n");

		while (1) {
			int len = recv(sock, response, sizeof(response) - 1, 0);

			if (len < 0) {
				printf("Error reading response\n");
				return 0;
			}

			if (len == 0) {
				break;
			}

			response[len] = 0;

			char* newline = strstr(response, "\r\n\r\n");
			if (newline != NULL) {
				newline += strlen("\r\n\r\n");
				printf("response:substring[%d]:%s", strlen(newline), newline);
			} else {
				printf("response[%d]:%s", len, response);
				continue;
			}

			len = strlen(newline);
			printf("Response[%d]: %s\n", len, newline);

			struct weather temp_results;
			ret = json_obj_parse(newline, len,
							weather_descr,
							ARRAY_SIZE(weather_descr),
							&temp_results);

			if (ret < 0)
			{
				LOG_ERR("JSON Parse Error: %d", ret);
			}
			else
			{
				LOG_INF("json_obj_parse return code: %d", ret);
				LOG_INF("Status: %s", temp_results.status);

				if(temp_results.lives_len>0) {
					LOG_INF("province: %s", temp_results.lives[0].province);
					LOG_INF("city: %s", temp_results.lives[0].city);
					LOG_INF("adcode: %s", temp_results.lives[0].adcode);
					LOG_INF("weather: %s", temp_results.lives[0].weather);
					LOG_INF("temperature: %s", temp_results.lives[0].temperature);
					LOG_INF("winddirection: %s", temp_results.lives[0].winddirection);
					LOG_INF("windpower: %s", temp_results.lives[0].windpower);
					LOG_INF("humidity: %s", temp_results.lives[0].humidity);
					LOG_INF("reporttime: %s", temp_results.lives[0].reporttime);
					LOG_INF("temperature_float: %s", temp_results.lives[0].temperature_float);
					LOG_INF("humidity_float: %s", temp_results.lives[0].humidity_float);
				}
			}
		}
		printf("\n");
		(void)close(sock);
		k_sleep(K_MSEC(10000));
	}

	return 0;
}

 

在上述代码中,CONFIG_NET_SOCKETS_SOCKOPT_TLS宏定义用于检查是否要发起HTTPS请求,这个在prj.conf中已经有定义。

具体各部分说明如下:

1) 头文件调用:

// 网络连接
#include <zephyr/net/socket.h>
#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
#include <zephyr/net/tls_credentials.h>
#include "ca_certificate.h"
#endif

需要添加tls以及证书相关的头文件。

 

2) HTTPS网址定义:

#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
#define HTTP_HOST "restapi.amap.com"
#define HTTP_PORT "443"
#endif
#define HTTP_PATH "/v3/weather/weatherInfo?key=【你的API_KEY】&city=110000&extensions=base"

上述地址对应高德天气API接口:

https://restapi.amap.com/v3/weather/weatherInfo?key=【你的API_KEY】&city=110000&extensions=base

 

3) JSON数据结构定义:

// JSON
#include <zephyr/data/json.h>

/*
{
	"status": "1",
	"count": "1",
	"info": "OK",
	"infocode": "10000",
	"lives": [
		{
			"province": "北京",
			"city": "北\ufffd\ufffd\ufffd市",
			"adcode": "110000",
			"weather": "晴",
			"temperature": "-1",
			"winddirection": "东",
			"windpower": "≤3",
			"humidity": "57",
			"reporttime": "2023-12-03 22:10:22",
			"temperature_float": "-1.0",
			"humidity_float": "57.0"
		}
	]
}
*/

struct weather_live {
	const char *province;
	const char *city;
	const char *adcode;
	const char *weather;
	const char *temperature;
	const char *winddirection;
	const char *windpower;
	const char *humidity;
	const char *reporttime;
	const char *temperature_float;
	const char *humidity_float;
};

struct weather {
	const char *status;
	const char *count;
	const char *info;
	const char *infocode;
	struct weather_live lives[1];
	size_t lives_len;
};

static const struct json_obj_descr weather_live_descr[] = {
  JSON_OBJ_DESCR_PRIM(struct weather_live, province, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct weather_live, city, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct weather_live, adcode, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct weather_live, weather, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct weather_live, temperature, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct weather_live, winddirection, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct weather_live, windpower, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct weather_live, humidity, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct weather_live, reporttime, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct weather_live, temperature_float, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct weather_live, humidity_float, JSON_TOK_STRING)
};

static const struct json_obj_descr weather_descr[] = {
	JSON_OBJ_DESCR_PRIM(struct weather, status, JSON_TOK_STRING),
	JSON_OBJ_DESCR_PRIM(struct weather, count, JSON_TOK_STRING),
	JSON_OBJ_DESCR_PRIM(struct weather, info, JSON_TOK_STRING),
	JSON_OBJ_DESCR_PRIM(struct weather, infocode, JSON_TOK_STRING),
	JSON_OBJ_DESCR_OBJ_ARRAY(struct weather, lives, 1, lives_len, weather_live_descr, ARRAY_SIZE(weather_live_descr)),
};
// JSON

通过高德天气APIJ接口返回的数据为JSON格式:

{
	"status": "1",
	"count": "1",
	"info": "OK",
	"infocode": "10000",
	"lives": [
		{
			"province": "北京",
			"city": "北\ufffd\ufffd\ufffd市",
			"adcode": "110000",
			"weather": "晴",
			"temperature": "-1",
			"winddirection": "东",
			"windpower": "≤3",
			"humidity": "57",
			"reporttime": "2023-12-03 22:10:22",
			"temperature_float": "-1.0",
			"humidity_float": "57.0"
		}
	]
}

需要参考这个格式,使用JSON库的定义方式,逐级定义weather_live、weather。

 

4) 加载证书:

#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
	tls_credential_add(CA_CERTIFICATE_TAG, TLS_CREDENTIAL_CA_CERTIFICATE,
			   ca_certificate, sizeof(ca_certificate));
#endif

 

5) 发起TLS请求:

#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
			sock = socket(res->ai_family, res->ai_socktype, IPPROTO_TLS_1_2);
#else
			sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
#endif

从上述代码中可以看到,TLS请求与普通HTTP时发起的请求类似,不过协议发生了变化。

 

6) 设置socket发送TLS请求需要的信息:

#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
		sec_tag_t sec_tag_opt[] = {
			CA_CERTIFICATE_TAG,
		};
		CHECK(setsockopt(sock, SOL_TLS, TLS_SEC_TAG_LIST,
				sec_tag_opt, sizeof(sec_tag_opt)));

		CHECK(setsockopt(sock, SOL_TLS, TLS_HOSTNAME,
				HTTP_HOST, sizeof(HTTP_HOST)))
#endif

 

7) 解析返回的JSON数据

成功发起请求获得数据后,后续的处理,就和之前HTTP请求类似了,最终输出代码如下:

				if(temp_results.lives_len>0) {
					LOG_INF("province: %s", temp_results.lives[0].province);
					LOG_INF("city: %s", temp_results.lives[0].city);
					LOG_INF("adcode: %s", temp_results.lives[0].adcode);
					LOG_INF("weather: %s", temp_results.lives[0].weather);
					LOG_INF("temperature: %s", temp_results.lives[0].temperature);
					LOG_INF("winddirection: %s", temp_results.lives[0].winddirection);
					LOG_INF("windpower: %s", temp_results.lives[0].windpower);
					LOG_INF("humidity: %s", temp_results.lives[0].humidity);
					LOG_INF("reporttime: %s", temp_results.lives[0].reporttime);
					LOG_INF("temperature_float: %s", temp_results.lives[0].temperature_float);
					LOG_INF("humidity_float: %s", temp_results.lives[0].humidity_float);
				}

 

五、运行测试

代码处理后之后,编译并烧录到nRF7002-DK开发板,最终运行输出的结果如下:

 

从上面的输出可以看到,成功发起了HTTPS请求,从高德天气API接口获取到了数据,并进行解析。

如果你还给板子接了OLED之类的显示屏,就可以显示到显示屏上输出,做为一个小小的天气牌了。

 

如果在运行的时候,输出如下信息:

恭喜你,这个时候,通常就是证书不对,或者不符合Mbed-TLS的要求了。我反复遇到过。

 

六、总结

在嵌入式设备上发起SSL/TLS请求,确实要比通用Linux、Windows、macOS上面麻烦不少。

不过,掌握了正确的用法,也会相当的方便。

nRF7002-DK上的Zephyr,提供了Mbed-TLS,使得相关的操作处理非常简单了。

另外,在不同的平台上,使用 Mbed-TLS 的方式,其实大同小异,掌握了一个平台的,其他平台上可以很快的就实施上。

 
此帖出自RF/无线论坛
点赞 关注
 

回复
举报
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
快速回复 返回顶部 返回列表