Arduino-ESP32通过socket和python或MFC进行通信,点灯和温湿度

#include <WiFi.h>
#include <WiFiClient.h>
#include <ArduinoJson.h>

const char* ssid = "your_SSID";
const char* password = "your_PASSWORD";
const char* server_host = "192.168.1.100"; // Python Socket服务器所在主机的IP地址
const int server_port = 12345; // Python Socket服务器监听的端口号

WiFiClient client;
String response;

void setup() {
  Serial.begin(9600);
  delay(1000);

  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  Serial.println("Connected to WiFi");
}

void loop() {
  if (!client.connected()) {
    Serial.print("Connecting to ");
    Serial.print(server_host);
    Serial.print(":");
    Serial.println(server_port);

    if (client.connect(server_host, server_port)) {
      Serial.println("Connected to server");
    } else {
      Serial.println("Connection failed");
      return;
    }
  }

  float temp = 24.5; // 假设读取到的温度为24.5°C
  float humid = 50.0; // 假设读取到的湿度为50%

  StaticJsonDocument<200> doc;
  doc["temp"] = temp;
  doc["humid"] = humid;
  doc["led"] = false; // 默认关闭LED
  serializeJson(doc, response);

  Serial.print("Sending message: ");
  Serial.println(response);

  client.print(response);

  delay(5000); // 每隔5秒钟发送一次数据

  if (client.available()) {
    String message = client.readStringUntil('\n');
    Serial.print("Received message: ");
    Serial.println(message);
    StaticJsonDocument<200> doc;
    DeserializationError error = deserializeJson(doc, message);

    if (!error) {
      bool led_state = doc["led"];
      Serial.print("LED state: ");
      Serial.println(led_state);

      // 根据收到的LED状态控制LED
      // do something...
    } else {
      Serial.println("Failed to parse message");
    }
  }
}

实现了连接Wi-Fi网络、创建Socket客户端并向Python Socket服务器发送温湿度和LED状态信息。在发送数据前,我们使用ArduinoJson库构造JSON格式的数据,并将其存储在response字符串变量中。之后,我们通过client.print()方法将这个字符串发送给Python Socket服务器。

同时,我们还使用了client.available()方法判断Python Socket服务器是否有回复消息。如果有,我们使用client.readStringUntil('\n')方法读取这个消息并解析其中的LED状态。需要注意的是,在解析收到的JSON消息时,我们仍然使用了ArduinoJson库。

import tkinter as tk
from ttkbootstrap import Style
import socket
import json

# 定义全局变量存储温度、湿度和LED状态的信息
temp = 0.0
humid = 0.0
led_state = False


def create_ui():
    # 创建主窗口
    root = tk.Tk()
    root.title("Socket服务器")
    root.geometry('600x400')
    style = Style(theme='flatly')

    # 创建Frame容器
    frame_top = tk.Frame(root)
    frame_top.pack(pady=20)

    frame_bottom = tk.Frame(root)
    frame_bottom.pack(pady=10)

    # 添加Label组件
    label_temp = tk.Label(frame_top, text="温度", font=("Helvetica", 18))
    label_temp.pack(side=tk.LEFT, padx=5)

    label_humid = tk.Label(frame_top, text="湿度", font=("Helvetica", 18))
    label_humid.pack(side=tk.LEFT, padx=5)

    # 添加仪表盘组件
    from tkinter import Canvas

    temp_val = tk.StringVar()
    temp_val.set(f"{temp} ℃")

    humid_val = tk.StringVar()
    humid_val.set(f"{humid} %")

    canvas_temp = Canvas(frame_top, width=100, height=100, bg='white')
    canvas_temp.pack(side=tk.LEFT, padx=20)

    canvas_humid = Canvas(frame_top, width=100, height=100, bg='white')
    canvas_humid.pack(side=tk.LEFT, padx=20)

    def update_gauge():
        # 更新温度仪表盘
        temp_val.set(f"{temp} ℃")
        canvas_temp.delete("all")
        canvas_temp.create_arc(10, 10, 90, 90, start=45, extent=270, style=tk.ARC, outline='red', width=2)
        canvas_temp.create_arc(10, 10, 90, 90, start=45, extent=270 * temp / 50, style=tk.ARC, outline='green', width=5)
        canvas_temp.create_text(50, 50, text=temp_val.get(), font=("Helvetica", 18))

        # 更新湿度仪表盘
        humid_val.set(f"{humid} %")
        canvas_humid.delete("all")
        canvas_humid.create_arc(10, 10, 90, 90, start=45, extent=270, style=tk.ARC, outline='blue', width=2)
        canvas_humid.create_arc(10, 10, 90, 90, start=45, extent=270 * humid / 100, style=tk.ARC, outline='green',
                                width=5)
        canvas_humid.create_text(50, 50, text=humid_val.get(), font=("Helvetica", 18))

        # 定时更新仪表盘
        root.after(1000, update_gauge)

    # 初始更新仪表盘
    update_gauge()

    # 添加按钮组件
    from tkinter import ttk

    button_text = tk.StringVar()
    button_text.set("开灯" if not led_state else "关灯")

    def click_button():
        global led_state
        led_state = not led_state
        socket_send({"led": led_state, "temp": temp, "humid": humid})

        button_text.set("关灯" if led_state else "开灯")

    button = ttk.Button(frame_bottom, textvariable=button_text, command=click_button)
    button.pack(side=tk.LEFT, padx=10)

    return root


def socket_server():
    # 创建Socket对象
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 绑定IP地址和端口号
    host = '127.0.0.1'
    port = 12345
    s.bind((host, port))

    # 监听连接
    s.listen(5)

    print("等待连接...")

    while True:
        # 接受客户端连接请求
        c, addr = s.accept()
        print("连接地址:", addr)

        while True:
            try:
                # 接收消息
                data = c.recv(1024).decode()

                # 解析JSON格式的消息并更新全局变量
                msg = json.loads(data)
                global temp, humid, led_state
                temp = msg["temp"]

                humid = msg["humid"]
                led_state = msg["led"]

                # 发送响应消息
                response_msg = json.dumps({"led": led_state}).encode()
                c.send(response_msg)

            except Exception as e:
                print(f"接收异常: {e}")
                break

        # 关闭客户端连接
        c.close()


def socket_send(msg):
    # 创建Socket对象
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 连接服务器
    host = '127.0.0.1'
    port = 12345
    s.connect((host, port))
    print('发送消息:', msg)

    try:
        # 发送消息
        sent_bytes = s.send(json.dumps(msg).encode())
        while sent_bytes < len(json.dumps(msg)):
            sent_bytes += s.send(json.dumps(msg[sent_bytes:]).encode())

        # 接收响应消息
        response_data = s.recv(1024).decode()
        response_json = json.loads(response_data)
        global led_state
        led_state = response_json['led']

    except Exception as e:
        print(f"发送异常: {e}")

    finally:
        # 关闭连接
        s.close()


if __name__ == '__main__':
    # 创建GUI界面
    root = create_ui()

    # 启动Socket服务器
    import threading

    t_server = threading.Thread(target=socket_server)
    t_server.start()

    # 进入主循环
    root.mainloop()

socket_server()函数添加了一个无限循环,等待客户端持续不断地发送消息给服务器。接收到消息之后,服务器会立即回复一个包含LED状态信息的响应消息。这样可以保证客户端和服务器间始终保持连接状态。

模拟测试数据

import socket
import json
import random
import time


def socket_send(msg):
    # 创建Socket对象
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 连接服务器
    host = '127.0.0.1'
    port = 12345
    s.connect((host, port))

    try:
        # 发送消息
        sent_bytes = s.send(json.dumps(msg).encode())
        while sent_bytes < len(json.dumps(msg)):
            sent_bytes += s.send(json.dumps(msg[sent_bytes:]).encode())

        # 接收响应消息
        response_data = s.recv(1024).decode()
        response_json = json.loads(response_data)

        # 打印 LED 状态
        led_state = response_json.get('led', None)
        if led_state is not None:
            led_status = '关闭' if not led_state else '打开'
            print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}][LED] {led_status}")

    except Exception as e:
        print(f"发送异常: {e}")

    finally:
        # 关闭连接
        s.close()


while True:
    # 模拟不断生成温度、湿度数据和 LED 数据
    temp = round(random.uniform(18, 28), 1)
    humid = round(random.uniform(30, 80), 1)
    led = bool(random.getrandbits(1))
    data = {"temp": temp, "humid": humid, "led": led}

    # 调用socket_send()将数据发送给服务器
    socket_send(data)
    print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}][发送成功] 温度={temp}℃,湿度={humid}%,LED={'打开' if led else '关闭'}")

    # 等待1秒钟后再次发送数据
    time.sleep(1)

MFC

// MyServerDlg.cpp : implementation file
//

#include "stdafx.h"
#include "MyServer.h"
#include "MyServerDlg.h"
#include "afxdialogex.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// CMyServerDlg dialog



CMyServerDlg::CMyServerDlg(CWnd* pParent /*=nullptr*/)
    : CDialogEx(IDD_MYSERVER_DIALOG, pParent)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
    m_bLedOn = FALSE;
}

void CMyServerDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_TEMP_EDIT, m_tempEdit);
    DDX_Control(pDX, IDC_HUMID_EDIT, m_humidEdit);
}

BEGIN_MESSAGE_MAP(CMyServerDlg, CDialogEx)
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(IDC_LED_BTN, &CMyServerDlg::OnBnClickedLedBtn)
END_MESSAGE_MAP()


// CMyServerDlg message handlers

BOOL CMyServerDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    // Set the icon for this dialog.  The framework does this automatically
    //  when the application's main window is not a dialog
    SetIcon(m_hIcon, TRUE);         // Set big icon
    SetIcon(m_hIcon, FALSE);        // Set small icon

    // TODO: Add extra initialization here
    m_server.Create(8080, SOCK_STREAM);
    m_server.Listen();

    return TRUE;  // return TRUE  unless you set the focus to a control
}

void CMyServerDlg::OnPaint()
{
    if (IsIconic())
    {
        CPaintDC dc(this); // device context for painting

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        // Center icon in client rectangle
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // Draw the icon
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialogEx::OnPaint();
    }
}

// The system calls this function to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CMyServerDlg::OnQueryDragIcon()
{
    return static_cast<HCURSOR>(m_hIcon);
}

void CMyServerDlg::OnBnClickedLedBtn()
{
    // TODO: Add your control notification handler code here
    m_bLedOn = !m_bLedOn;

    CString strLed;
    if (m_bLedOn)
    {
        strLed = _T("关灯");
    }
    else
    {
        strLed = _T("开灯");
    }
    GetDlgItem(IDC_LED_BTN)->SetWindowText(strLed);

    // send message to ESP32
    CString strMsg;
    strMsg.Format(_T("{\"led\":%d}"), m_bLedOn);
    SendToClient(strMsg);
}

void CMyServerDlg::SendToClient(CString strMsg)
{
    for (int i = 0; i < m_clients.GetCount(); i++)
    {
        SOCKET sock = m_clients.GetAt(i);

        int len = strMsg.GetLength();
        char* buf = new char[len + 1];
        memset(buf, 0, len + 1);
        WideCharToMultiByte(CP_ACP, 0, strMsg, -1, buf, len + 1, NULL, NULL);

        send(sock, buf, len, 0);

        delete[] buf;
    }
}

void CMyServerDlg::OnAccept()
{
    SOCKET sock = m_server.Accept();

    m_clients.Add(sock);

    AfxBeginThread(ClientThread, (LPVOID)sock);
}

UINT CMyServerDlg::ClientThread(LPVOID lpParam)
{
    SOCKET sock = (SOCKET)lpParam;

    char buf[1024];
    int len;
    CString strMsg;
    while ((len = recv(sock, buf, 1024, 0)) > 0)
    {
        buf[len] = 0;

        strMsg += CString(buf);

        int pos = strMsg.Find('\n');
        while (pos >= 0)
        {
            CString strJson = strMsg.Left(pos);
            strMsg = strMsg.Mid(pos + 1);

            Json::Reader reader;
            Json::Value root;
            if (reader.parse(strJson.GetBuffer(0), root))
            {
                if (root.isMember("temp"))
                {
                    double temp = root["temp"].asDouble();
                    CString strTemp;
                    strTemp.Format(_T("%.1f ℃"), temp);
                    AfxGetMainWnd()->SendMessage(WM_UPDATE_TEMP, (WPARAM)&strTemp, 0);
                }
                if (root.isMember("humid"))
                {
                    double humid = root["humid"].asDouble();
                    CString strHumid;
                    strHumid.Format(_T("%.1f %%"), humid);
                    AfxGetMainWnd()->SendMessage(WM_UPDATE_HUMID, (WPARAM)&strHumid, 0);
                }
            }

            pos = strMsg.Find('\n');
        }
    }

    closesocket(sock);

    ((CMyServerDlg*)AfxGetMainWnd())->m_clients.Remove(sock);

    return 0;
}

LRESULT CMyServerDlg::OnUpdateTemp(WPARAM wParam, LPARAM lParam)
{
    CString* pStr = (CString*)wParam;
    m_tempEdit.SetWindowText(*pStr);
    delete pStr;

    return 0;
}

LRESULT CMyServerDlg::OnUpdateHumid(WPARAM wParam, LPARAM lParam)
{
    CString* pStr = (CString*)wParam;
    m_humidEdit.SetWindowText(*pStr);
    delete pStr;

    return 0;
}

其中,CMyServerDlg是我们的主窗口类,它继承自CDialogEx。在OnInitDialog中,我们创建了一个socket服务器,并监听端口8080。在OnAccept中,我们接受了一个客户端连接,并创建了一个新的线程ClientThread来处理这个客户端的消息。

ClientThread中,我们使用了JsonCpp库来解析从客户端发送过来的json消息,并根据消息内容更新界面上的温度和湿度。

OnBnClickedLedBtn中,我们通过SendToClient函数向客户端发送了一个消息,来控制LED的开关。在SendToClient函数中,我们遍历了所有连接的客户端,并向它们发送了消息。

OnUpdateTempOnUpdateHumid中,我们分别更新了界面上的温度和湿度文本框的内容。

需要注意的是,我们在ClientThread中使用了SendMessage函数来更新界面,而不是直接操作界面上的控件。这是因为MFC是单线程模型,不能在非主线程中直接操作界面,否则会导致程序崩溃。因此,我们需要使用SendMessage函数来将更新界面的任务转移到主线程中执行。

基于ESP32的Arduino代码的示例,实现了将DHT11传感器采集到的温度和湿度数据发送到MFC创建的socket服务器,并接收服务器发送过来的LED控制指令:

#include <WiFi.h>
#include <WiFiClient.h>
#include <DHT.h>
#include <ArduinoJson.h>

#define DHTPIN 4
#define DHTTYPE DHT11

const char* ssid = "your_SSID";
const char* password = "your_PASSWORD";
const char* host = "192.168.1.100"; // MFC服务器的IP地址
const int port = 8080; // MFC服务器监听的端口号

DHT dht(DHTPIN, DHTTYPE);

WiFiClient client;

void setup()
{
  Serial.begin(9600);
  delay(1000);

  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");

  dht.begin();
}

void loop()
{
  float temp = dht.readTemperature();
  float humid = dht.readHumidity();

  if (isnan(temp) || isnan(humid)) {
    Serial.println("Failed to read from DHT sensor!");
    delay(1000);
    return;
  }

  // send data to server
  StaticJsonDocument<64> doc;
  doc["temp"] = temp;
  doc["humid"] = humid;
  String strJson;
  serializeJson(doc, strJson);
  strJson += "\n"; // 消息以换行符结束
  if (!client.connected()) {
    if (!connectToServer()) {
      return;
    }
  }
  client.print(strJson);

  // receive data from server
  while (client.available()) {
    String line = client.readStringUntil('\n');
    StaticJsonDocument<32> doc;
    DeserializationError error = deserializeJson(doc, line);
    if (error) {
      Serial.println("Failed to parse JSON!");
      return;
    }
    if (doc.containsKey("led")) {
      bool led = doc["led"];
      digitalWrite(LED_BUILTIN, led ? LOW : HIGH);
      // send response to server
      StaticJsonDocument<16> doc;
      doc["led"] = led;
      String strJson;
      serializeJson(doc, strJson);
      strJson += "\n";
      client.print(strJson);
    }
  }

  delay(1000);
}

bool connectToServer()
{
  Serial.print("Connecting to server...");
  if (client.connect(host, port)) {
    Serial.println("connected!");
    return true;
  }
  else {
    Serial.println("connection failed!");
    return false;
  }
}

  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
2022 / 01/ 30: 新版esptool 刷micropython固件指令不是 esptool.py cmd... 而是 esptool cmd... 即可;另外rshell 在 >= python 3.10 的时候出错解决方法可以查看:  已于2022年发布的: 第二章:修复rshell在python3.10出错 免费内容: https://edu.csdn.net/course/detail/29666 micropython语法和python3一样,编写起来非常方便。如果你快速入门单片机玩物联网而且像轻松实现各种功能,那绝力推荐使用micropython。方便易懂易学。 同时如果你懂C语音,也可以用C写好函数并编译进micropython固件里然后进入micropython调用(非必须)。 能通过WIFI联网(2.1章),也能通过sim卡使用2G/3G/4G/5G联网(4.5章)。 为实现语音控制,本教程会教大家使用tensorflow利用神经网络训练自己的语音模型并应用。为实现通过网页控制,本教程会教大家linux(debian10 nginx->uwsgi->python3->postgresql)网站前后台入门。为记录单片机传输过来的数据, 本教程会教大家入门数据库。  本教程会通过通俗易懂的比喻来讲解各种原理与思路,并手把手编写程序来实现各项功能。 本教程micropython版本是 2019年6月发布的1.11; 更多内容请看视频列表。  学习这门课程之前你需要至少掌握: 1: python3基础(变量, 循环, 函数, 常用库, 常用方法)。 本视频使用到的零件与淘宝上大致价格:     1: 超声波传感器(3)     2: MAX9814麦克风放大模块(8)     3: DHT22(15)     4: LED(0.1)     5: 8路5V低电平触发继电器(12)     6: HX1838红外接收模块(2)     7:红外发射管(0.1),HX1838红外接收板(1)     other: 电表, 排线, 面包板(2)*2,ESP32(28)  
### 回答1: Arduino-ESP32ModbusRTU是一个基于Arduino平台和ESP32芯片的Modbus RTU通信协议库。Modbus RTU是一种常用的串行通信协议,用于在不同设备之间实现通信和数据交换。ESP32是一款强大的Wi-Fi和蓝牙芯片,具有丰富的资源和功能。 利用Arduino-ESP32ModbusRTU库,我们可以轻松地在ESP32上实现Modbus RTU通信功能。通过这个库,我们可以将ESP32作为Modbus RTU的主机或从机,并与其他Modbus RTU设备进行通信。在主机模式下,ESP32可以向其他设备发送Modbus RTU命令,并接收其响应。在从机模式下,ESP32可以接收其他设备发送的Modbus RTU命令,并作出相应的响应。 使用Arduino-ESP32ModbusRTU库,我们可以使用简单的代码实现Modbus RTU通信功能。我们可以指定串口用于与其他设备进行通信,并设置自己的Modbus地址。我们可以使用库提供的函数来读取和写入寄存器或线圈,以及进行其他Modbus操作。 Arduino-ESP32ModbusRTU库还支持异步通信功能,可以在不阻塞主线程的情况下进行Modbus通信操作。这使得我们可以同时处理其他任务,而不会影响Modbus通信的性能和稳定性。 总之,Arduino-ESP32ModbusRTU是一个方便实用的库,可以帮助我们在ESP32上实现Modbus RTU通信功能。无论是作为主机还是从机,通过这个库,我们可以轻松地与其他Modbus RTU设备进行数据交换和通信。 ### 回答2: arduino-esp32modbusrtu是一种基于Arduino开发平台和ESP32微控制器的Modbus RTU通信库。Modbus是一种通信协议,常用于工业自动化领域中的设备间通信arduino-esp32modbusrtu库为ESP32提供了实现Modbus RTU通信的功能,可以让ESP32作为Modbus RTU主机或从机来与其他设备进行通信。通过该库,用户可以轻松地对Modbus RTU数据进行读取和写入。 使用arduino-esp32modbusrtu库,用户首先需要在Arduino开发平台上导入该库,并在代码中包含相应的头文件。然后,需要设置串口参数,并创建一个Modbus RTU对象。用户可以根据需要选择将ESP32配置为主机或从机,并指定Modbus设备的地址。 在主机模式下,用户可以使用ModbusRTUMaster类的方法来发送读取或写入请求,并获取设备的响应。用户可以指定读取或写入的寄存器地址以及读取的数量或写入的值。在从机模式下,用户可以使用ModbusRTUSlave类的方法来处理主机的请求,并返回相应的数据。 arduino-esp32modbusrtu库提供了许多示例代码和详细的文档,帮助用户快速上手,并实现其Modbus RTU通信需求。使用该库,用户可以自定义设置通信参数、处理各种Modbus功能码,并与其他Modbus RTU设备进行稳定可靠的通信。 总之,arduino-esp32modbusrtu是一款强大而灵活的Modbus RTU通信库,为基于ESP32的项目提供了方便快捷的Modbus功能支持。 ### 回答3: Arduino-ESP32ModbusRTU是一种基于ESP32微控制器的Modbus RTU通信协议库。该库允许使用Arduino编程语言开发工具与Modbus RTU设备进行通信。 Modbus RTU是一种在串行通信介质上实现的开放式通信协议。使用Modbus RTU协议,可以实现数据在不同设备之间的传输和控制。通常,Modbus RTU是在RS485物理层上实现的,允许多个设备共享通信线路。 Arduino-ESP32ModbusRTU库通过处理Modbus RTU帧格式,实现了Master和Slave两种角色的操作。作为Master,ESP32可以通过发送请求到Slave设备来读取或写入数据。而作为Slave,ESP32可以接收Master设备发送的请求,并根据请求进行数据读取或写入。 使用Arduino-ESP32ModbusRTU库,我们可以通过Arduino编程语言轻松地实现Modbus RTU通信。我们可以设定串行通信参数(如波特率、数据位、停止位等),并使用预定义的函数来读取或写入Modbus寄存器中的数据。此外,该库还支持不同种类的Modbus寄存器,如输入寄存器、保持寄存器、线圈和离散输入寄存器。 总结而言,Arduino-ESP32ModbusRTU库为我们提供了一种简单而高效的方式来实现ESP32与其他Modbus RTU设备之间的通信。无论是作为Master还是Slave,ESP32都可以通过这个库与Modbus RTU设备进行数据的读取和写入。这个库的使用使得我们可以很方便地将ESP32应用于各种Modbus RTU通信场景中,如工业自动化、设备监控等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值