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°Cfloat humid = 50.0; // 假设读取到的湿度为50%StaticJsonDocument<200> doc;doc["temp"] = temp;doc["humid"] = humid;doc["led"] = false; // 默认关闭LEDserializeJson(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 = Falsedef 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 Canvastemp_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 ttkbutton_text = tk.StringVar()button_text.set("开灯" if not led_state else "关灯")def click_button():global led_stateled_state = not led_statesocket_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 rootdef socket_server():# 创建Socket对象s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 绑定IP地址和端口号host = '127.0.0.1'port = 12345s.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_statetemp = 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 = 12345s.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_stateled_state = response_json['led']except Exception as e:print(f"发送异常: {e}")finally:# 关闭连接s.close()if __name__ == '__main__':# 创建GUI界面root = create_ui()# 启动Socket服务器import threadingt_server = threading.Thread(target=socket_server)t_server.start()# 进入主循环root.mainloop()
socket_server()
函数添加了一个无限循环,等待客户端持续不断地发送消息给服务器。接收到消息之后,服务器会立即回复一个包含LED状态信息的响应消息。这样可以保证客户端和服务器间始终保持连接状态。
模拟测试数据
import socket
import json
import random
import timedef socket_send(msg):# 创建Socket对象s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 连接服务器host = '127.0.0.1'port = 12345s.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 dialogCMyServerDlg::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 handlersBOOL CMyServerDlg::OnInitDialog()
{CDialogEx::OnInitDialog();// Set the icon for this dialog. The framework does this automatically// when the application's main window is not a dialogSetIcon(m_hIcon, TRUE); // Set big iconSetIcon(m_hIcon, FALSE); // Set small icon// TODO: Add extra initialization herem_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 paintingSendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);// Center icon in client rectangleint 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 icondc.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 herem_bLedOn = !m_bLedOn;CString strLed;if (m_bLedOn){strLed = _T("关灯");}else{strLed = _T("开灯");}GetDlgItem(IDC_LED_BTN)->SetWindowText(strLed);// send message to ESP32CString 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
函数中,我们遍历了所有连接的客户端,并向它们发送了消息。
在OnUpdateTemp
和OnUpdateHumid
中,我们分别更新了界面上的温度和湿度文本框的内容。
需要注意的是,我们在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 DHT11const 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 serverStaticJsonDocument<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 serverwhile (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 serverStaticJsonDocument<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;}
}