1112|6

330

帖子

5

TA的资源

纯净的硅(中级)

楼主
 

【nRF7002-DK Wi-Fi® 6开发套件评测】WiFi联网请求JSON数据控制LED [复制链接]

本篇评测,展示了通过nRF7002-DK自身的WiFi能力联网,然后调用Zephyr提供的BSD Socket API发起HTTP请求,获取JSON数据,然后解析JSON数据,根据JSON数据控制板载LED。

 

一、知识了解

要实现本篇评测展示的暖色,需要预先对以下的部分进行了解。

首先是联网,可以查看 sdk-nrf/samples/wifi/sta,在该演示中,展示了Zephyr的Network Management能力。

然后,查看zephyr/samples/net/sockets/http_get,在该演示中,展示了Zephyr提供的BSD Socket兼容API,用于通过socket发起HTTP请求。对应的官方BSD Socket说明可以查看: https://docs.zephyrproject.org/latest/connectivity/networking/api/sockets.html 

另外,查看 https://docs.zephyrproject.org/apidoc/latest/group__json.html,了解Zephyr提供的json解析库。

 

最后,查看 zephyr/samples/basic/blinky,在该演示中,展示了Zephyr如何控制LED。

 

二、代码说明

本篇评测,展示功能对应的代码如下:

/*
 * Copyright (c) 2022 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
 */

/** [url=home.php?mod=space&uid=1307177]@File[/url] * [url=home.php?mod=space&uid=159083]@brief[/url] WiFi station sample
 */

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(sta, 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>

#define HTTP_HOST "192.168.1.100"
#define HTTP_PORT "18080"
#define HTTP_PATH "/api/test"

#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>

struct myinfo {
	const char *name;
	int timestamp;
	int status;
};

static const struct json_obj_descr myinfo_descr[] = {
  JSON_OBJ_DESCR_PRIM(struct myinfo, name, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct myinfo, timestamp, JSON_TOK_NUMBER),
  JSON_OBJ_DESCR_PRIM(struct myinfo, status, JSON_TOK_NUMBER),
};

// GPIO
#define LED1_NODE DT_ALIAS(led1)
static const struct gpio_dt_spec led1 = GPIO_DT_SPEC_GET(LED1_NODE, gpios);

#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)
{
	int ret;

	if (!gpio_is_ready_dt(&led1)) {
		return 0;
	}

	ret = gpio_pin_configure_dt(&led1, GPIO_OUTPUT_ACTIVE);
	if (ret < 0) {
		return 0;
	}

	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;

	printf("Preparing HTTP GET request for http://" HTTP_HOST
	       ":" HTTP_PORT HTTP_PATH "\n");

	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.");

		sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

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

		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(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");
			if (newline != NULL) {
				continue;
			}

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

			struct myinfo temp_results;
			ret = json_obj_parse(response, len,
							myinfo_descr,
							ARRAY_SIZE(myinfo_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("Name: %s", temp_results.name);
				LOG_INF("Timestamp: %d", temp_results.timestamp);
				LOG_INF("Status: %d", temp_results.status);

				LOG_INF("Led: %s", temp_results.status?"ON":"OFF");
				gpio_pin_set_dt(&led1, temp_results.status?1:0);
			}
		}
		printf("\n");
		(void)close(sock);
		k_sleep(K_MSEC(10000));
	}

	return 0;
}

 

在上述代码中,对各部分的重点功能进行说明:

1. WiFi联网:

代码中,wifi_connect()用于WiFi联网,联网的具体配置在prj.conf中:

CONFIG_WIFI=y
CONFIG_WIFI_NRF700X=y

# WPA supplicant
CONFIG_WPA_SUPP=y

# Below configs need to be modified based on security
# CONFIG_STA_KEY_MGMT_NONE=y
CONFIG_STA_KEY_MGMT_WPA2=y
# CONFIG_STA_KEY_MGMT_WPA2_256=y
# CONFIG_STA_KEY_MGMT_WPA3=y
CONFIG_STA_SAMPLE_SSID="OpenBSD"
CONFIG_STA_SAMPLE_PASSWORD="88888888"

启动WiFi联网后,通过 context.connect_result 判断当前的联网状态,通过 context.connected 判断是否联网成功。

 

2. HTTP请求

在代码中,网络部分的相关链接定义如下:

// 网络连接
#include <zephyr/net/socket.h>

#define HTTP_HOST "192.168.1.100"
#define HTTP_PORT "18080"
#define HTTP_PATH "/api/test"

static char response[1024];

上述定义,访问的网址为:  http://192.168.1.100:18080/api/test    注意:ip根据你的实际情况而定。

我用Python写了一个最最最简单的WEB服务,用于提供JSON数据,具体代码如下:

from http.server import BaseHTTPRequestHandler, HTTPServer
import time
import json

status = False
# 定义一个HTTP请求处理程序类
class MyHTTPRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        global status
        status = not status
        # 返回JSON格式的时间戳
        response_body = json.dumps({"name":"test", "timestamp": int(time.time()), "status": 1 if status else 0})
        self.send_response(200)
        self.send_header("Content-type", "application/json")
        self.send_header('Content-length', str(len(response_body)))
        self.end_headers()
        self.wfile.write(response_body.encode())

# 启动Web服务并监听指定端口
server_address = ('0.0.0.0', 18080)
httpd = HTTPServer(server_address, MyHTTPRequestHandler)
print('启动Web服务,监听端口:', server_address[1])
httpd.serve_forever()

 

使用python运行上述代码,然后请求上述网址,将会返回:

{"name": "test", "timestamp": 1701599737, "status": 1}

 

在代码中,请求上述网址,使用了标准的BSD Socket API: socket()、connect()、send()、recv(),最终将会得到对应的请求信息输出:

 

3. JSON解析

本来想用cJSON,不过发现Zephyr已经提供了JSON解析库,正好用上。

要使用json库,需要先在prj.conf里面添加对应的配置:

# JSON
CONFIG_JSON_LIBRARY=y

然后在代码中添加头文件:

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

为了解析上面的json字符串,需要预先做一些对应的结构定义,具体如下:

struct myinfo {
	const char *name;
	int timestamp;
	int status;
};

static const struct json_obj_descr myinfo_descr[] = {
  JSON_OBJ_DESCR_PRIM(struct myinfo, name, JSON_TOK_STRING),
  JSON_OBJ_DESCR_PRIM(struct myinfo, timestamp, JSON_TOK_NUMBER),
  JSON_OBJ_DESCR_PRIM(struct myinfo, status, JSON_TOK_NUMBER),
};

上述代码,定义了与请求返回的json字符串一致的数据格式。

在收到HTTP请求返回的数据以后,进行解析:

			struct myinfo temp_results;
			ret = json_obj_parse(response, len,
							myinfo_descr,
							ARRAY_SIZE(myinfo_descr),
							&temp_results);

如果解析成功,就能够调用解析后的各个属性了:

				LOG_INF("Name: %s", temp_results.name);
				LOG_INF("Timestamp: %d", temp_results.timestamp);
				LOG_INF("Status: %d", temp_results.status);

 

4. LED控制:

首先,从 sdk-nrf/boards/arm/nrf7002dk_nrf5340/nrf5340_cpuapp_common.dts 中,可以得知预定义的LED:

	aliases {
		led0 = &led0;
		led1 = &led1;
		pwm-led0 = &pwm_led0;
		sw0 = &button0;
		sw1 = &button1;
		bootloader-led0 = &led0;
		mcuboot-button0 = &button0;
		mcuboot-led0 = &led0;
	};

wifi_sta不分,已经使用了LED0,所以我们自己控制,就使用LED1。

代码中对应的定义如下:

// GPIO
#define LED1_NODE DT_ALIAS(led1)
static const struct gpio_dt_spec led1 = GPIO_DT_SPEC_GET(LED1_NODE, gpios);

实际控制也比较简单,根据json返回的status,来设置0、1即可控制:

gpio_pin_set_dt(&led1, temp_results.status?1:0);

 

三、运行测试:

在vscode的NRF Connect中,按照如下步骤进行编译和烧录:

 

 

编译Build成功结果如下:

 

 

烧录Flash成功结果如下:

 

 

烧录完成后,开发板会启动,开始WiFi连接:

 

然后搜索WiFi热点,并进行连接:

 

 

 

连接成功后,就会发起HTTP请求:

 

然后解析获取到的JSON数据:

 

 

并通过status的值,来控制LED1的亮灭。

 

四、总结

Zephyr的功能非常的完善,给的实例有多,越用越觉得好用。

联网能力是nRF7002-DK的基本能力,在此基础上,能够完成很多实际的工作。

后续还会继续分享,如何进行HTTPS请求,来获取和解析数据。

 

此帖出自无线连接论坛

最新回复

这个是真不错,就是还不太会玩啊     详情 回复 发表于 2023-12-10 08:47
点赞 关注
 

回复
举报

7056

帖子

11

TA的资源

版主

沙发
 

乔老师的帖子,看着就是收获大,期待更加精彩的帖子呀!

此帖出自无线连接论坛
 
 

回复

6561

帖子

9

TA的资源

版主

板凳
 

有没有比较方便的PC端用于布局控制信号的?不用自己写代码的,接口可以自动生成的

此帖出自无线连接论坛

点评

    mqtt dashboard  详情 回复 发表于 2023-12-9 14:49
node-red应该可以。代码很少。  详情 回复 发表于 2023-12-7 17:14
个人签名

在爱好的道路上不断前进,在生活的迷雾中播撒光引

 
 
 

回复

6107

帖子

4

TA的资源

版主

4
 
本帖最后由 damiaa 于 2023-12-7 17:15 编辑
秦天qintian0303 发表于 2023-12-4 11:31 有没有比较方便的PC端用于布局控制信号的?不用自己写代码的,接口可以自动生成的

node-red应该可以。图形化,代码很少。

 

楼主评测搞得好!

此帖出自无线连接论坛
 
 
 

回复

330

帖子

5

TA的资源

纯净的硅(中级)

5
 
秦天qintian0303 发表于 2023-12-4 11:31 有没有比较方便的PC端用于布局控制信号的?不用自己写代码的,接口可以自动生成的

16.实际效果

 

 

mqtt dashboard

此帖出自无线连接论坛

点评

这个是真不错,就是还不太会玩啊    详情 回复 发表于 2023-12-10 08:47
 
 
 

回复

6561

帖子

9

TA的资源

版主

6
 

这个是真不错,就是还不太会玩啊  

此帖出自无线连接论坛

点评

手机端的其实很简单,也不需要编程。    详情 回复 发表于 2023-12-14 23:42
个人签名

在爱好的道路上不断前进,在生活的迷雾中播撒光引

 
 
 

回复

330

帖子

5

TA的资源

纯净的硅(中级)

7
 
秦天qintian0303 发表于 2023-12-10 08:47 这个是真不错,就是还不太会玩啊  

手机端的其实很简单,也不需要编程。

 

此帖出自无线连接论坛
 
 
 

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

查找数据手册?

EEWorld Datasheet 技术支持

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