多个ESP32CAM,根据端口号的不同,来各自控制,实现拍照上传和开灯。(一次一张图片)
下位机代码:
#include <Arduino.h>
#include <WiFi.h>
#include "esp_camera.h"
#include <vector>
const char *ssid = "**";//自己决定
const char *password = "**";//自己决定
uint16_t serverPort = 8080; //服务器端口号
String esp32_cam_head = "esp32_cam_head:"; //esp32cam与服务器的通信标识头
//String esp32cam_to_esp32 = "esp32cam_to_esp32:";
bool cam_state = true; //打开或关闭摄像头标识
bool flashRequired = true; //闪光灯,true是打开闪光灯
const int brightLED = 4;//闪光灯
const int ZHESHI_LED = 33; //指示灯 亮表示连接上服务器 灭表示没有连上服务器
const IPAddress gatewayIP; // Declare gatewayIP at the global level
#define maxcache 1430 //图像数据包的大小
WiFiClient client; //声明一个客户端对象,用于与服务器进行连接
// Bright LED (Flash)
const int ledFreq = 50; // PWM settings
const int ledChannel = 15; // camera uses timer1
const int ledRresolution = 80; // resolution (8 = from 0 to 255)根据PWM控制亮度
// change illumination LED brightness
void brightLed(byte ledBrightness) {
ledcWrite(ledChannel, ledBrightness); // change LED brightness (0 - 255)
Serial.println("Brightness changed to " + String(ledBrightness));
}
// ----------------------------------------------------------------r
// set up PWM for the illumination LED (flash)
// ----------------------------------------------------------------
// note: I am not sure PWM is very reliable on the esp32cam - requires more testing
void setupFlashPWM() {
ledcSetup(ledChannel, ledFreq, ledRresolution);
ledcAttachPin(brightLED, ledChannel);
brightLed(0);
}
//CAMERA_MODEL_AI_THINKER类型摄像头的引脚定义
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
static camera_config_t camera_config = {
.pin_pwdn = PWDN_GPIO_NUM,
.pin_reset = RESET_GPIO_NUM,
.pin_xclk = XCLK_GPIO_NUM,
.pin_sscb_sda = SIOD_GPIO_NUM,
.pin_sscb_scl = SIOC_GPIO_NUM,
.pin_d7 = Y9_GPIO_NUM,
.pin_d6 = Y8_GPIO_NUM,
.pin_d5 = Y7_GPIO_NUM,
.pin_d4 = Y6_GPIO_NUM,
.pin_d3 = Y5_GPIO_NUM,
.pin_d2 = Y4_GPIO_NUM,
.pin_d1 = Y3_GPIO_NUM,
.pin_d0 = Y2_GPIO_NUM,
.pin_vsync = VSYNC_GPIO_NUM,
.pin_href = HREF_GPIO_NUM,
.pin_pclk = PCLK_GPIO_NUM,
.xclk_freq_hz = 20000000,
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG,
.frame_size = FRAMESIZE_XGA, //图片格式
.jpeg_quality = 12,
.fb_count = 1,
};
//初始化摄像头
esp_err_t camera_init() {
//initialize the camera
esp_err_t err = esp_camera_init(&camera_config);
if (err != ESP_OK) {
Serial.println( "Camera Init Failed");//esp32cam_to_esp32 +
return err;
}
sensor_t * s = esp_camera_sensor_get();
//initial sensors are flipped vertically and colors are a bit saturated
if (s->id.PID == OV2640_PID) {
// s->set_vflip(s, 1);//flip it back
// s->set_brightness(s, 1);//up the blightness just a bit
// s->set_contrast(s, 1);
}
Serial.println( "Camera Init OK!");//esp32cam_to_esp32 +
return ESP_OK;
}
//初始化wifi
void wifi_init()
{
WiFi.mode(WIFI_STA);
WiFi.setSleep(false); //关闭STA模式下wifi休眠,提高响应速度
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
delay(100);
// Serial.println(esp32cam_to_esp32 + ".");
}
// 获取默认网关地址
IPAddress gatewayIP = WiFi.gatewayIP();
Serial.print("Gateway IP address: ");
Serial.println(gatewayIP);
Serial.println( "Connected");//esp32cam_to_esp32 +
}
void setup()
{
Serial.begin(115200);
pinMode(ZHESHI_LED, OUTPUT);
digitalWrite(ZHESHI_LED, HIGH);
pinMode(brightLED, OUTPUT); // flash LED
digitalWrite(brightLED, LOW); // led off = Low
wifi_init();
camera_init();
setupFlashPWM();
}
//拍照
void cssp(){
camera_fb_t * fb = esp_camera_fb_get();
uint8_t * temp = fb->buf; //这个是为了保存一个地址,在摄像头数据发送完毕后需要返回,否则会出现板子发送一段时间后自动重启,不断重复
if (!fb)
{
Serial.println("Camera Capture Failed");
}
else
{
//先发送Frame Begin 表示开始发送图片 然后将图片数据分包发送 每次发送1430 余数最后发送
//完毕后发送结束标志 Frame Over 表示一张图片发送完毕
client.print("Frame Begin"); //一张图片的起始标志
// 将图片数据分段发送
int leng = fb->len;
int timess = leng/maxcache;
int extra = leng%maxcache;
for(int j = 0;j< timess;j++)
{
client.write(fb->buf, maxcache);
for(int i =0;i< maxcache;i++)
{
fb->buf++;
}
}
client.write(fb->buf, extra);
client.print("Frame Over"); // 一张图片的结束标志
//Serial.print("This Frame Length:");
//Serial.print(fb->len);
//Serial.println(".Succes To Send Image For TCP!");
//return the frame buffer back to the driver for reuse
fb->buf = temp; //将当时保存的指针重新返还
esp_camera_fb_return(fb); //这一步在发送完毕后要执行,具体作用还未可知。
}
//delay(20);//短暂延时 增加数据传输可靠性
}
void loop()
{
if (client.connect(WiFi.gatewayIP(), serverPort)) //尝试访问目标地址
{
digitalWrite(ZHESHI_LED, LOW);
while (client.connected() || client.available()) //如果已连接或有收到的未读取的数据
{
if (client.available()) //如果tcp网络有数据可读取 读取网络数据
{
String line = client.readStringUntil('\n'); //读取数据到换行符
// 开始拍照
if (line == (esp32_cam_head + "takePhoto") ){
Serial.println("拍照中...");
cssp();
Serial.println("照片数据发送完毕");
}
// 打开闪光灯
if(line == (esp32_cam_head + "openLed") ){
brightLed(255);
delay(300);
(esp32_cam_head + "Led ON!");
}
// 关闭闪光灯
if(line == (esp32_cam_head + "closeLed") ){
brightLed(0);
(esp32_cam_head + "Led OFF!");
}
}
}
}
else
{
digitalWrite(ZHESHI_LED, HIGH);
Serial.println( "Connect To Tcp Server Failed!After 3 Seconds Try Again!");//esp32cam_to_esp32 +
client.stop(); //关闭客户端
}
delay(1000);
}
上面是自动寻找所连WiFi的IP地址 ,与该ip地址服务端进行互通,而想要固定ip的话,前面要先加一段
//const IPAddress serverIP(*,*,*,*); //欲访问的地址
//uint16_t serverPort = ****; //服务器端口号
//const IPAddress staticIP(*, *, *, *); // 设置静态IP地址
//const IPAddress gateway(*, *, *, *); // 设置网关
//const IPAddress subnet(255, 255, 255, 0); // 设置子网掩码
然后下面WiFi函数改成
//初始化wifi
bool wifi_init(const char* ssid,const char* password ){
WiFi.mode(WIFI_STA);
WiFi.setSleep(false); //关闭STA模式下wifi休眠,提高响应速度
#ifdef staticIP
WiFi.config(staticIP, gateway, subnet);
#endif
WiFi.begin(ssid, password);
uint8_t i = 0;
Serial.println();
while (WiFi.status() != WL_CONNECTED && i++ < 20) {
delay(500);
Serial.print(".");
}
if (i == 21) {
Serial.println();
Serial.print("Could not connect to");
Serial.println(ssid);
digitalWrite(ZHESHI_LED,HIGH); //网络连接失败 熄灭指示灯
return false;
}
Serial.print("Connecting to wifi ");
Serial.print(ssid);
Serial.println(" success!");
digitalWrite(ZHESHI_LED,LOW); //网络连接成功 点亮指示灯
return true;
}
具体细节可能需要自己改一下,因为主要是和app联动,下面的上位机也只是测试使用。
上位机代码:
import socket
import threading
import os
import time, sys
import io
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QHBoxLayout, QFileDialog, QLineEdit, QApplication
from PyQt5.QtGui import QPixmap, QImage
from PyQt5.QtCore import Qt
from PyQt5 import QtCore, QtGui, QtWidgets
port_list = [**, **, **, **, **] # 要监听的端口号列表
save_folder = "images" # 指定保存图片的文件夹名称
save_state = False
begin_data = b'Frame Begin'
end_data = b'Frame Over'
esp32_cam_head = b'esp32_cam_head:' # 发送和接受esp32cam消息的标志头
client_list = [] #列表存放客户端对象
class PhotoViewer(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('Photo Viewer')
self.setGeometry(100, 100, 1200, 600)
self.save_button = QPushButton('全部保存', self)
self.save_button.setGeometry(QtCore.QRect(350, 400, 75, 23))
self.save_button.setObjectName("save_button")
self.save_button.setCheckable(True)
self.save_button.clicked.connect(self.savePhoto)
self.open_led_button = QPushButton('打开闪光灯', self)
self.open_led_button.setGeometry(QtCore.QRect(450, 400, 75, 23))
self.open_led_button.setObjectName("open_led_button")
# self.open_led_button.setCheckable(True)
self.open_led_button.clicked.connect(self.open_led)
self.close_led_button = QPushButton('关闭闪光灯', self)
self.close_led_button.setGeometry(QtCore.QRect(550, 400, 75, 23))
self.close_led_button.setObjectName("close_led_button")
# self.close_led_button.setCheckable(True)
self.close_led_button.clicked.connect(self.close_led)
self.take_photo_button = QPushButton('拍照', self)
self.take_photo_button.setGeometry(QtCore.QRect(650, 400, 75, 23))
self.take_photo_button.setObjectName("take_photo_button")
# self.take_photo_button.setCheckable(True)
self.take_photo_button.clicked.connect(self.take_photo)
self.image_label_1 = QLabel(self)
self.image_label_1.setGeometry(QtCore.QRect(200, 200, 6, 12))
self.image_label_1.setText("8040")
self.image_label_2 = QLabel(self)
self.image_label_2.setGeometry(QtCore.QRect(240, 200, 6, 12))
self.image_label_2.setText("8050")
self.image_label_3 = QLabel(self)
self.image_label_3.setGeometry(QtCore.QRect(400, 200, 6, 12))
self.image_label_3.setText("8060")
self.image_label_4 = QLabel(self)
self.image_label_4.setGeometry(QtCore.QRect(560, 200, 6, 12))
self.image_label_4.setText("8070")
self.image_label_5 = QLabel(self)
self.image_label_5.setGeometry(QtCore.QRect(720, 200, 6, 12))
self.image_label_5.setText("8080")
self.image_label_1.setMaximumSize(300, 200)
self.image_label_2.setMaximumSize(300, 200)
self.image_label_3.setMaximumSize(300, 200)
self.image_label_4.setMaximumSize(300, 200)
self.image_label_5.setMaximumSize(300, 200)
# # 本地端口
# self.port_label = QLabel("本地端口:", self)
# self.port_label.setGeometry(750, 400, 75, 23)
# self.port_edit = QLineEdit("9090", self)
# self.port_edit.setGeometry(820, 400, 75, 23)
self.ipconfig = QLabel(self)
self.ipconfig.setAlignment(Qt.AlignCenter)
layout = QHBoxLayout()
layout.addWidget(self.image_label_1)
layout.addWidget(self.image_label_2)
layout.addWidget(self.image_label_3)
layout.addWidget(self.image_label_4)
layout.addWidget(self.image_label_5)
self.setLayout(layout)
self.current_image = None
# # 创建一个 QTimer,用于触发界面更新
# self.timer = QTimer(self)
# self.timer.timeout.connect(self.updateImage)
# 启动 QTimer,设置刷新间隔为100毫秒
# self.timer.start(100)
def savePhoto(self):
global save_state
if save_state:
save_state = False
self.save_button.setText("不保存")
else:
save_state = True
self.save_button.setText("保存")
# 打开ESP32cam闪光灯 别忘记发送数据带 esp32_cam_head
def open_led(self):
for s in client_list:
try:
s.send(esp32_cam_head + 'openLed'.encode())
except:
try:
s.close()
client_list.remove(s)
except:
print("Client is Closed!")
# 关闭ESP32cam闪光灯 别忘记发送数据带 esp32_cam_head
def close_led(self):
for s in client_list:
try:
s.send(esp32_cam_head + 'closeLed'.encode())
except:
try:
s.close()
client_list.remove(s)
except:
print("Client is Closed!")
def take_photo(self):
for s in client_list:
try:
s.send(esp32_cam_head + 'takePhoto'.encode())
except:
try:
s.close()
client_list.remove(s)
except:
print("Client is Closed!")
def updateImage(self):
# 在 QTimer 触发的槽函数中更新图片
global port
if self.current_image is not None:
# 创建QImage对象
img = QImage(self.current_image.data, self.current_image.shape[1], self.current_image.shape[0],
self.current_image.strides[0], QImage.Format_RGB888)
# 创建QPixmap对象并设置到界面上
pixmap = QPixmap.fromImage(img)
if port == port_list[0]:
# if port == int(self.port_edit.text()):
self.image_label_1.setPixmap(pixmap)
self.image_label_1.setScaledContents(True)
# if port == int(self.port_edit.text()):
if port == port_list[1]:
self.image_label_2.setPixmap(pixmap)
self.image_label_2.setScaledContents(True)
# if port == int(self.port_edit.text()):
if port == port_list[2]:
self.image_label_3.setPixmap(pixmap)
self.image_label_3.setScaledContents(True)
if port == port_list[3]:
self.image_label_4.setPixmap(pixmap)
self.image_label_4.setScaledContents(True)
if port == port_list[4]:
self.image_label_5.setPixmap(pixmap)
self.image_label_5.setScaledContents(True)
def handle_sock(sock, addr, viewer):
print('----------开始接收-------')
temp_data = b''
t1 = int(round(time.time() * 1000))
times = [0, 0, 0, 0, 0]
while True:
data = sock.recv(1430)
# 如果这一帧数据包的开头是 b'Frame Begin' 则是一张图片的开始
if data[0:len(begin_data)] == begin_data:
# 将这一帧数据包的开始标志信息(b'Frame Begin')清除 因为它不属于图片数据
data = data[len(begin_data):len(data)]
# 判断这一帧数据流是不是最后一个帧 最后一针数据的结尾时b'Frame Over'
while data[-len(end_data):] != end_data:
temp_data = temp_data + data # 不是结束的包 讲数据添加进temp_data
data = sock.recv(1430) # 继续接受数据 直到接受的数据包包含b'Frame Over' 表示是这张图片的最后一针
# 判断为最后一个包 将数据去除 结束标志信息 b'Frame Over'
temp_data = temp_data + data[0:(len(data) - len(end_data))] # 将多余的(\r\nFrame Over)去掉 其他放入temp_data
# 使用io.BytesIO创建临时缓冲区,接收完整的图片数据
image_data = io.BytesIO(temp_data)
# 将io.BytesIO对象中的数据转换为bytes类型
image_bytes = image_data.getvalue()
# 创建QImage对象
img = QImage.fromData(image_bytes)
# 刷新界面,确保图片显示更新
QApplication.processEvents()
# 创建QPixmap对象并设置到界面上
pixmap = QPixmap.fromImage(img)
# 根据客户端的端口号保存图片
port = sock.getsockname()[1]
if(port == port_list[0]):
viewer.image_label_1.setPixmap(pixmap)
viewer.image_label_1.setScaledContents(True)
if (port == port_list[1]):
viewer.image_label_2.setPixmap(pixmap)
viewer.image_label_2.setScaledContents(True)
if (port == port_list[2]):
viewer.image_label_3.setPixmap(pixmap)
viewer.image_label_3.setScaledContents(True)
if (port == port_list[3]):
viewer.image_label_4.setPixmap(pixmap)
viewer.image_label_4.setScaledContents(True)
if (port == port_list[4]):
viewer.image_label_5.setPixmap(pixmap)
viewer.image_label_5.setScaledContents(True)
print(f"port:{port}")
if save_state:
file_name = os.path.join(save_folder, f'eyes_{port}_{times[port_list.index(port)]}.jpg')
times[port_list.index(port)] += 1 #一直拍照
# file_name = os.path.join(save_folder, f'eyes_{port}.jpg') #覆盖同一张照片
with open(file_name, 'wb') as fp:
fp.write(temp_data)
print("保存成功")
# 重置temp_data为空字节串
temp_data = b''
print('------接收下一张图片--------')
# esp32cam发送过来的数据 标志位 b'esp32_cam_head:'
if data[0:len(esp32_cam_head)] == esp32_cam_head:
print('来自ESP32CAM的消息:' + data.decode())
def listen_port(port, viewer):
# 创建服务器监听指定端口
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host_name = socket.gethostname()
local_ip = socket.gethostbyname(host_name)
server.bind((local_ip, port))
print(local_ip)
server.listen(5)
while True:
# 阻塞连接 info一个socket对象
info, addr = server.accept()
client_list.append(info) # 有客户端连接 将客户端对象放在列表
print('Connect--{}'.format(addr))
# 连接成功后开一个线程用于处理客户端
client_thread = threading.Thread(target=handle_sock, args=(info, addr, viewer))
client_thread.start()
def cv2qpixmap(cv_img):
height, width, channel = cv_img.shape
bytesPerLine = 3 * width
return QImage(cv_img.data, width, height, bytesPerLine, QImage.Format_RGB888)
if __name__ == '__main__':
# 创建保存图片的文件夹
if not os.path.exists(save_folder):
os.makedirs(save_folder)
app = QApplication(sys.argv)
window = PhotoViewer()
# 创建线程监听多个端口
threads = []
for port in port_list:
t = threading.Thread(target=listen_port, args=(port, window))
t.start()
threads.append(t)
window.show()
sys.exit(app.exec_())