HonestQiao 发表于 2023-12-3 19:02

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

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

<p>&nbsp;</p>

<p>一、知识了解</p>

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

<p>首先是联网,可以查看&nbsp;sdk-nrf/samples/wifi/sta,在该演示中,展示了Zephyr的<a data-jsarwt="1" data-usg="AOvVaw0pzXhkgaO8sj8ZKa2nJ8fg" data-ved="2ahUKEwiqseORhvOCAxV8sVYBHbSeD1wQFnoECAgQAQ" href="https://docs.zephyrproject.org/apidoc/latest/group__net__mgmt.html" jsname="UWckNb" rel="noopener" target="_blank">Network Management</a>能力。</p>

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

<p>另外,查看&nbsp;<a href="https://docs.zephyrproject.org/apidoc/latest/group__json.html" target="_blank">https://docs.zephyrproject.org/apidoc/latest/group__json.html</a>,了解Zephyr提供的json解析库。</p>

<p>&nbsp;</p>

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

<p>&nbsp;</p>

<p>二、代码说明</p>

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

<pre>
<code class="language-cpp">/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/

/** @File * @brief WiFi station sample
*/

#include &lt;zephyr/logging/log.h&gt;
LOG_MODULE_REGISTER(sta, CONFIG_LOG_DEFAULT_LEVEL);

#include &lt;nrfx_clock.h&gt;
#include &lt;zephyr/kernel.h&gt;
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;zephyr/shell/shell.h&gt;
#include &lt;zephyr/sys/printk.h&gt;
#include &lt;zephyr/init.h&gt;

#include &lt;zephyr/net/net_if.h&gt;
#include &lt;zephyr/net/wifi_mgmt.h&gt;
#include &lt;zephyr/net/net_event.h&gt;
#include &lt;zephyr/drivers/gpio.h&gt;

#include &lt;qspi_if.h&gt;

#include "net_private.h"

// 网络连接
#include &lt;zephyr/net/socket.h&gt;

#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;
// 网络连接

// JSON
#include &lt;zephyr/data/json.h&gt;

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(&amp;led, GPIO_OUTPUT_ACTIVE);
        if (ret &lt; 0) {
                LOG_ERR("Error %d: failed to configure LED pin", ret);
                return;
        }

        while (1) {
                if (context.connected) {
                        gpio_pin_toggle_dt(&amp;led);
                        k_msleep(LED_SLEEP_TIME_MS);
                } else {
                        gpio_pin_set_dt(&amp;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, &amp;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 &gt;= WIFI_STATE_ASSOCIATED) {
                uint8_t mac_string_buf;

                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-&gt;info;

        if (context.connected) {
                return;
        }

        if (status-&gt;status) {
                LOG_ERR("Connection failed (%d)", status-&gt;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-&gt;info;

        if (!context.connected) {
                return;
        }

        if (context.disconnect_requested) {
                LOG_INF("Disconnection request %s (%d)",
                       status-&gt;status ? "failed" : "done",
                                        status-&gt;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-&gt;info;
        const struct in_addr *addr = &amp;dhcpv4-&gt;requested_ip;
        char dhcp_info;

        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-&gt;timeout =CONFIG_STA_CONN_TIMEOUT_SEC * MSEC_PER_SEC;

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

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

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

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

        /* MFP (optional) */
        params-&gt;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(&amp;cnx_params);

        if (net_mgmt(NET_REQUEST_WIFI_CONNECT, iface,
                     &amp;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;

        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 &lt; bytes_len; i++) {
                memcpy(byte_str, str + i * 2, 2);
                byte_str = '\0';
                bytes = strtol(byte_str, NULL, 16);
        }

        return 0;
}

int main(void)
{
        int ret;

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

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

        memset(&amp;context, 0, sizeof(context));

        net_mgmt_init_event_callback(&amp;wifi_shell_mgmt_cb,
                                     wifi_mgmt_event_handler,
                                     WIFI_SHELL_MGMT_EVENTS);

        net_mgmt_add_event_callback(&amp;wifi_shell_mgmt_cb);


        net_mgmt_init_event_callback(&amp;net_shell_mgmt_cb,
                                     net_mgmt_event_handler,
                                     NET_EVENT_IPV4_DHCP_BOUND);

        net_mgmt_add_event_callback(&amp;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;
                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 &lt; QSPI_KEY_LEN_BYTES; i++) {
                        LOG_DBG("%02x", key);
                }
                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 -&gt; %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, &amp;hints, &amp;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-&gt;ai_family, res-&gt;ai_socktype, res-&gt;ai_protocol);

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

                int ret = connect(sock, res-&gt;ai_addr, res-&gt;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 &lt; 0) {
                                printf("Error reading response\n");
                                return 0;
                        }

                        if (len == 0) {
                                break;
                        }

                        response = 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),
                                                        &amp;temp_results);

                        if (ret &lt; 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(&amp;led1, temp_results.status?1:0);
                        }
                }
                printf("\n");
                (void)close(sock);
                k_sleep(K_MSEC(10000));
        }

        return 0;
}
</code></pre>

<p>&nbsp;</p>

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

<p>1. WiFi联网:</p>

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

<pre>
<code class="language-ini">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"</code></pre>

<p>启动WiFi联网后,通过&nbsp;context.connect_result 判断当前的联网状态,通过&nbsp;context.connected 判断是否联网成功。</p>

<p>&nbsp;</p>

<p>2. HTTP请求</p>

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

<pre>
<code class="language-cpp">// 网络连接
#include &lt;zephyr/net/socket.h&gt;

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

static char response;</code></pre>

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

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

<pre>
<code class="language-python">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)
httpd.serve_forever()</code></pre>

<p>&nbsp;</p>

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

<pre>
<code class="language-json">{"name": "test", "timestamp": 1701599737, "status": 1}</code></pre>

<p>&nbsp;</p>

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

<p> &nbsp;</p>

<p>3. JSON解析</p>

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

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

<pre>
<code class="language-ini"># JSON
CONFIG_JSON_LIBRARY=y</code></pre>

<p>然后在代码中添加头文件:</p>

<pre>
<code class="language-cpp">// JSON
#include &lt;zephyr/data/json.h&gt;
</code></pre>

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

<pre>
<code class="language-cpp">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),
};</code></pre>

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

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

<pre>
<code class="language-cpp">                        struct myinfo temp_results;
                        ret = json_obj_parse(response, len,
                                                        myinfo_descr,
                                                        ARRAY_SIZE(myinfo_descr),
                                                        &amp;temp_results);</code></pre>

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

<pre>
<code class="language-cpp">                                LOG_INF("Name: %s", temp_results.name);
                                LOG_INF("Timestamp: %d", temp_results.timestamp);
                                LOG_INF("Status: %d", temp_results.status);</code></pre>

<p>&nbsp;</p>

<p>4. LED控制:</p>

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

<pre>
<code class="language-json">        aliases {
                led0 = &amp;led0;
                led1 = &amp;led1;
                pwm-led0 = &amp;pwm_led0;
                sw0 = &amp;button0;
                sw1 = &amp;button1;
                bootloader-led0 = &amp;led0;
                mcuboot-button0 = &amp;button0;
                mcuboot-led0 = &amp;led0;
        };</code></pre>

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

<p>代码中对应的定义如下:</p>

<pre>
<code class="language-cpp">// GPIO
#define LED1_NODE DT_ALIAS(led1)
static const struct gpio_dt_spec led1 = GPIO_DT_SPEC_GET(LED1_NODE, gpios);</code></pre>

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

<pre>
<code class="language-cpp">gpio_pin_set_dt(&amp;led1, temp_results.status?1:0);</code></pre>

<p>&nbsp;</p>

<p>三、运行测试:</p>

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

<p> &nbsp;</p>

<p>&nbsp;</p>

<p>编译Build成功结果如下:</p>

<p> &nbsp;</p>

<p>&nbsp;</p>

<p>烧录Flash成功结果如下:</p>

<p> &nbsp;</p>

<p>&nbsp;</p>

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

<p> &nbsp;</p>

<p>然后搜索WiFi热点,并进行连接:</p>

<p> &nbsp;</p>

<p> &nbsp;</p>

<p>&nbsp;</p>

<p>连接成功后,就会发起HTTP请求:</p>

<p> &nbsp;</p>

<p>然后解析获取到的JSON数据:</p>

<p> &nbsp;</p>

<p>&nbsp;</p>

<p>并通过status的值,来控制LED1的亮灭。</p>

<p>&nbsp;</p>

<p>四、总结</p>

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

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

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

<p>&nbsp;</p>
</div><script>                                        var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;"   style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
                                       
                                        if(parseInt(discuz_uid)==0){
                                                                                                (function($){
                                                        var postHeight = getTextHeight(400);
                                                        $(".showpostmsg").html($(".showpostmsg").html());
                                                        $(".showpostmsg").after(loginstr);
                                                        $(".showpostmsg").css({height:postHeight,overflow:"hidden"});
                                                })(jQuery);
                                        }                </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script>

lugl4313820 发表于 2023-12-3 19:33

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

秦天qintian0303 发表于 2023-12-4 11:31

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

damiaa 发表于 2023-12-7 17:14

本帖最后由 damiaa 于 2023-12-7 17:15 编辑

<div class="quote">
<blockquote><font size="2"><a href="forum.php?mod=redirect&amp;goto=findpost&amp;pid=3283613&amp;ptid=1265462" target="_blank"><font color="#999999">秦天qintian0303 发表于 2023-12-4 11:31</font></a></font> 有没有比较方便的PC端用于布局控制信号的?不用自己写代码的,接口可以自动生成的</blockquote>
</div>

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

<p>&nbsp;</p>

<p>楼主评测搞得好!<img height="26" src="https://bbs.eeworld.com.cn/static/editor/plugins/hkemoji/sticker/facebook/wanwan88.gif" width="32" /></p>

HonestQiao 发表于 2023-12-9 14:49

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

<p><img alt="16.实际效果" src="https://forum.rvspace.org/uploads/default/optimized/2X/6/6b2fe75d6afff975514ea1ecd4bf76d1db98823f_2_230x500.jpeg" /></p>

<p>&nbsp;</p>

<p>&nbsp;</p>

<p>mqtt dashboard</p>

秦天qintian0303 发表于 2023-12-10 08:47

HonestQiao 发表于 2023-12-9 14:49
&nbsp;

&nbsp;

mqtt dashboard

<p>这个是真不错,就是还不太会玩啊&nbsp;&nbsp;</p>

HonestQiao 发表于 2023-12-14 23:42

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

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

<p>&nbsp;</p>
页: [1]
查看完整版本: 【nRF7002-DK Wi-Fi® 6开发套件评测】WiFi联网请求JSON数据控制LED