ESP32和python将电脑屏幕投射到小屏幕
上位机和下位机的代码都会开源
思路简析
使用python的socket模块和ardunio的wifi库即可建立服务器和客户端打通电脑和ESP32的连接,传输速度相当可观。
打通连接之后再确定一个简单的协议即可,在这里确定了一个非常简单的协议,只需要两步:
1.建立连接后,下位机会立刻发送字符’L’给上位机,上位机收到后立刻发送这一帧的长度给下位机
2.下位机收到后再立刻发送字符‘Y’给上位机,上位机收到后立刻将这一帧图片发送给下位机,下位机解码输出后再发送’L’给上位机,如此循环。
上下位机的代码
上位机代码
需要的模块中除了PIL库需要额外下载外,其他库都是自带的。
PIL 库的下载也很简单,在cmd中输入下面这一句命令即可
pip install PIL
from tkinter import *
from socket import *
from tkinter import filedialog
import threading
from time import strftime
import os
from PIL import ImageGrab #截图库
#--------------------------------------------#
class myapp(Tk):
def __init__(self):
super().__init__()
self.title("完形填空")
self.geometry("240x340")
#b1=Button(self,text="投屏",command=self.toupin)
#b1.place(x=80,y=0,width=80,height=40) 这个按钮用作测试用,实际程序中不需要,所以注释掉
b2=Button(self,text="连接",command=self.lianjie)
b2.place(x=0,y=0,width=120,height=40)
b3=Button(self,text="关闭连接",command=self.guanbi)
b3.place(x=120,y=0,width=120,height=40)
self.text=Text(self)
self.text.place(x=0,y=40,width=240,height=60) #各种GUI控件,布局
self.s=socket(AF_INET,SOCK_STREAM)
self.add=('',6666)
self.s.bind(self.add)
self.s.listen(5) #服务器配置和开启监听
self.xian=1
def toupin(self):
while self.xian:
sou=self.tap.recv(1024)
if sou.decode()=='L':
pic = ImageGrab.grab((100,200,340,440)) #在这里设置截屏区域,分别是左上角坐标和右下角坐标
pic.save('1.jpg')
self.text.insert(END,strftime("%H:%M:%S")+': '+sou.decode()+'\n')
self.l=int(os.path.getsize('1.jpg'))
self.tap.send(int(os.path.getsize('1.jpg')).to_bytes(2,byteorder='little',signed=True))
elif sou.decode()=='Y':
self.text.insert(END,strftime("%H:%M:%S")+': '+sou.decode()+'\n')
self.file=open('1.jpg','rb+')
while self.l>512:
self.cc=self.file.read(512)
self.tap.send(self.cc)
self.l-=512
self.cc=self.file.read(self.l)
self.tap.send(self.cc)
self.file.close()
#收发过程可以理解为:1.下位机先给上位机发送'L',上位机收到后给下位机发送这一帧的长度
# 2.下位机收到长度后给上位机发送'Y',上位机收到后发送图片信息
# 3.下位机收到后解码输出图片后再继续发送'L',代表下一帧开始
#如此循环
#PS:上位机这里用了一个文件作中转站,作者实在是不知道如何将RGB通道改成jpg格式
# 有懂的朋友可以评论区告诉我一下
def lianjie(self):
self.tap,self.addr=self.s.accept()
print(self.addr)
self.xian=1
self.t1=threading.Thread(target=self.toupin)
self.t1.start()
def guanbi(self):
self.xian=0
if __name__=='__main__':
app1=myapp()
app1.mainloop()
下位机代码
下位机代码是C++语言,环境是Ardunio环境
//------------需要的库------------//
#include <TFT_eSPI.h>
#include <SPI.h>
#include <WiFi.h>
#include <TJpg_Decoder.h>
TFT_eSPI tft = TFT_eSPI();
//------在这里定义你的各种参数-------//
char* ssid = "WiFi-name"; //填写你的wifi名字
char* passwrd = ""; //填写你的wifi密码
char* service_ip="132.178.100.4";//上位机IP地址
int httpPort = 6666; //设置上位机端口
//----下面的参数用于loadingcartoon函数-----//
char tai=0,i;
char load[8][2]={{100,120},{110,110,},{120,100},{130,110},{140,120},{130,130},{120,140},{110,130}};
//-------以下参数用于收发图片--------------//
WiFiClient client; //初始化一个客户端对象
TaskHandle_t loading= NULL;
uint8_t buff[7000] PROGMEM= {0};//每一帧的临时缓存
uint8_t img_buff[40000] PROGMEM= {0};//用于存储tcp传过来的图片,注意图片大小不要超出内存,分辨率高的屏幕可以扩容
uint16_t size_count=0;//计算一帧的字节大小
uint16_t read_count,s_time,e_time,total_count=0;
uint8_t changdu[2];
//------------------------------------//
bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap)//jpg解码回调函数
{
if ( y >= tft.height() ) return 0;
tft.pushImage(x, y, w, h, bitmap);
// Return 1 to decode next block
return 1;
}
//------------------------------------------------------------------//
void loadingcartoon(void *pvParameters) // This is a task.
{
//(void) pvParameters;
for (;;) // A Task shall never return or exit.
{
switch(tai)
{
case 0:
for(i=0;i<=7;i++)
{
tft.fillCircle(load[i][0],load[i][1],2,TFT_RED);
//delay(100);
vTaskDelay(100);
}
tai=1;
break;
case 1:
for(i=0;i<=7;i++)
{
tft.fillCircle(load[i][0],load[i][1],4,TFT_RED);
//delay(100);
vTaskDelay(100);
}
tai=2;
break;
case 2:
for(i=0;i<=7;i++)
{
tft.fillCircle(load[i][0],load[i][1],4,TFT_WHITE);
//delay(100);
vTaskDelay(100);
}
tai=0;
break;
}
}
}
//----------------------------------------------------//
void setup(){
Serial.begin(115200);
tft.begin();
tft.setRotation(0);//竖屏240x240
tft.fillScreen(TFT_WHITE);//白屏
tft.setTextColor(TFT_BLACK,TFT_WHITE);
tft.fillRect(20, 100,200,20, TFT_WHITE);
tft.drawString("conecting..",30,150,4);
xTaskCreate(loadingcartoon,"loading",1024,NULL,1,&loading); //开启动画线程
WiFi.begin(ssid, pasword); //连接wifi
delay(1000); //等待1秒
while (WiFi.status() != WL_CONNECTED) {
for(byte n=0;n<10;n++){ //每500毫秒检测一次状态
//loading(50);
delay(500);
}
}
if (WiFi.status() == WL_CONNECTED) //判断如果wifi连接成功
{
client.connect(service_ip,httpPort); //连接到上位机
Serial.println("wifi is connected!");
Serial.print("SSID: ");
Serial.println(WiFi.SSID());
IPAddress ip = WiFi.localIP();
Serial.print("IP Address: ");
Serial.println(ip);
}
else Serial.printf("connect feid");
vTaskDelete(loading); //连接成功后删除线程
tft.drawString("conected....",30,150,4);
delay(2000);
tft.fillScreen(TFT_BLACK);
TJpgDec.setJpgScale(1);
TJpgDec.setSwapBytes(true);
TJpgDec.setCallback(tft_output);//解码成功回调函数
}
//-------------------------------------------------//
void loop(){
s_time=millis();
client.write("L");
delay(1);
while(1)
{
if(client.available())
{
read_count=client.read(changdu,1024);
size_count=changdu[0]+changdu[1]*0x100;
Serial.println(size_count);
read_count=0;
client.write("Y");
while(total_count<size_count)
{
if(client.available())
{
read_count=client.read(buff,7000);//向缓冲区读取数据
memcpy(&img_buff[total_count],buff,read_count);//将读取的buff字节地址复制给img_buff数组
total_count+=read_count;
}
}
Serial.println(total_count);
TJpgDec.drawJpg(0,0,img_buff, sizeof(img_buff));//将jpg图片解码为bmp
memset(&img_buff,0,sizeof(img_buff));//清空buff
total_count=0;
}
else continue;
break;
}
e_time=millis();
Serial.printf("当前处理能力:");
Serial.print(1000/(e_time-s_time),2);
Serial.printf("Fps\n");
}
新下载的TJpg_Decoder可能会编译错误,而且他还需要一个较为冷门的库,在这里献上我调试好的库,只需要将之复制粘贴到Ardunio的library中即可。
链接: 点击这里下载库文件 链接:https://pan.baidu.com/s/1VmfNl7qXzZvYhMSolFg1UA?pwd=w7e2
提取码:w7e2
其实官网下载的TJpg_Decoder库中也就只有一个错误而已,在编译的时候就会发现其使用的littlefs改成与littlefs库文件中的头文件一致即可。
总结
tkinter程序界面中留有240x240像素的空白,原本是给截图预留的展示区域,但是tkinter程序刷新图片会频闪,谁能解决评论区告诉一下。
在连接电脑的热点时,刷新能够达到15Fps左右。