LEDPanel_128x128/README.md

230 lines
7.4 KiB
Markdown
Raw Permalink Normal View History

2024-08-21 20:14:50 -07:00
# Project stream frame via websocket using ESP32 to LED Panel 128x128 with PlatformIO extension
2024-08-21 19:31:26 -07:00
2024-08-22 19:15:24 -07:00
### Pinout ESP32-S3
2024-08-21 19:31:26 -07:00
![](/img/connector.jpg)
2024-08-22 19:16:24 -07:00
```Cpp
2024-08-22 19:15:24 -07:00
#define R1_PIN 4
#define G1_PIN 5
#define B1_PIN 6
#define R2_PIN 7
#define G2_PIN 15
#define B2_PIN 16
#define A_PIN 18
#define B_PIN 8 // Changed from library default
#define C_PIN 3
#define D_PIN 42
#define E_PIN 39 // required for 1/32 scan panels, like 64x64px. Any available pin would do, i.e. IO32
#define LAT_PIN 40
#define OE_PIN 2
#define CLK_PIN 41
```
2024-08-21 19:31:26 -07:00
### Example code to send image/gif.video... to ESP32
```python
from PIL import Image, ImageSequence
import socket
import struct
import time
import cv2
import serial
from flask import Flask, request, redirect, make_response
import os
import asyncio
import websockets
import threading
### Flask configuration
app = Flask(__name__)
UPLOAD_FOLDER = 'uploads'
RESIZED_FOLDER = 'resized'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
os.makedirs(RESIZED_FOLDER, exist_ok=True)
### Socket configuration
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_ip = '192.168.2.243' # IP của ESP32
udp_port = 5000
uri = "ws://192.168.2.243:81"
media_path = '/home/qthi/Documents/quyth/Image/128x128/gif2.gif'
# To manage the WebSocket thread
websocket_thread = None
websocket_stop_event = threading.Event()
### Page
@app.route('/')
def upload_form():
return '''
<html><body>
<h1>Upload Image</h1>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="Upload">
</form>
</body></html>
'''
frames = []
@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return redirect(request.url)
file = request.files['file']
if file.filename == '':
return redirect(request.url)
if file:
filepath = os.path.join(UPLOAD_FOLDER, file.filename)
file.save(filepath)
frames = resize_gif_and_get_colors(filepath, (128, 128))
threading.Thread(target=start_websocket_thread, args=(frames,)).start()
# return send_file(resized_filepath, as_attachment=True)
return make_response('OK',200)
def resize_gif_and_get_colors(input_path, new_size):
"""
Thay đổi kích thước từng khung hình của ảnh GIF và trả về mảng màu RGB565 cho từng khung hình.
:param input_path: Đường dẫn đến ảnh GIF gốc
:param new_size: Kích thước mới (width, height)
:return: Danh sách các mảng màu của từng khung hình
"""
frames_colors = []
with Image.open(input_path) as img:
for frame in ImageSequence.Iterator(img):
frame = frame.convert('RGB').resize(new_size, Image.ANTIALIAS)
width, height = frame.size
frame_data = bytearray()
for y in range(height):
for x in range(width):
r, g, b = frame.getpixel((x, y))
rgb565 = rgb888_to_rgb565(r, g, b)
frame_data.extend(struct.pack('!' + 'H', rgb565))
frames_colors.append((frame_data,width,height))
return frames_colors
def rgb888_to_rgb565(r, g, b):
"""Chuyển đổi từ RGB888 sang RGB565"""
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
def load_image_to_rgb565(file_path):
"""Tải tệp ảnh và chuyển đổi từng pixel sang định dạng RGB565"""
img = Image.open(file_path)
img = img.convert('RGB') # Đảm bảo ảnh ở định dạng RGB
width, height = img.size
frame_data = []
for y in range(height):
for x in range(width):
r, g, b = img.getpixel((x, y))
rgb565 = rgb888_to_rgb565(r, g, b)
frame_data.append(rgb565)
print(frame_data)
return frame_data
def load_gif_to_rgb565(file_path):
"""Tải tệp GIF và chuyển đổi từng khung hình sang định dạng RGB565"""
gif = Image.open(file_path)
frames = []
for frame in ImageSequence.Iterator(gif):
frame = frame.convert('RGB')
width, height = frame.size
frame_data = []
for y in range(height):
for x in range(width):
r, g, b = frame.getpixel((x, y))
rgb565 = rgb888_to_rgb565(r, g, b)
frame_data.append(rgb565)
frames.append((frame_data, width, height))
return frames
def load_video_to_rgb565(file_path):
"""Tải video và chuyển đổi từng khung hình sang định dạng RGB565"""
cap = cv2.VideoCapture(file_path)
frames = []
while True:
ret, frame = cap.read()
if not ret:
break
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
height, width = frame.shape[:2]
frame_data = []
for y in range(height):
for x in range(width):
r, g, b = frame[y, x]
rgb565 = rgb888_to_rgb565(r, g, b)
frame_data.append(rgb565)
frames.append((frame_data, width, height))
print(frames)
cap.release()
return frames
def send_frame(frame_data, udp_ip, udp_port):
"""Gửi khung hình qua UDP"""
packet_size = 1460 # Kích thước gói tối đa để tránh tràn UDP
num_packets = (len(frame_data) * 2 + packet_size - 1) // packet_size
for i in range(num_packets):
start_index = i * packet_size // 2
end_index = min((i + 1) * packet_size // 2, len(frame_data))
packet = struct.pack('!' + 'H' * (end_index - start_index), *frame_data[start_index:end_index])
sock.sendto(packet, (udp_ip, udp_port))
# time.sleep(0.1)
def send_data_via_serial(port, baudrate, data):
with serial.Serial(port, baudrate, timeout=1) as ser:
# Gửi mảng dữ liệu
for value in data:
# Gửi giá trị RGB565 (2 byte)
ser.write(struct.pack('<H', value))
def start_websocket_thread(frames):
global websocket_thread, websocket_stop_event
if websocket_thread and websocket_thread.is_alive():
websocket_stop_event.set()
websocket_thread.join()
websocket_stop_event.clear()
websocket_thread = threading.Thread(target=lambda:asyncio.run(send_image_data(frames,websocket_stop_event)))
websocket_thread.start()
# asyncio.run(send_image_data(frames))
async def send_image_data(frames, stop_event):
# Thay <ESP32_IP> bằng địa chỉ IP của ESP32
# uri = "ws://localhost:8765" # Thay <ESP32_IP> bằng địa chỉ IP của ESP32
#frames = load_gif_to_rgb565(file_path)
async with websockets.connect(uri) as websocket:
while not stop_event.is_set():
#frame_data = load_image_to_rgb565(file_path)
# frame_data = create_test_pattern()
for frame_data, _, _ in frames:
# Chia nhỏ dữ liệu nếu cần (tùy thuộc vào kích thước của dữ liệu và giới hạn của WebSocket)
chunk_size = 12288 # Kích thước mỗi phần dữ liệu
for i in range(0, len(frame_data), chunk_size):
chunk = frame_data[i:i + chunk_size]
await websocket.send(chunk)
await asyncio.sleep(1/10) # Giữ tốc độ gửi dữ liệu
def main():
app.run(debug=True)
if __name__ == "__main__":
main()
```
2024-08-21 20:00:24 -07:00
### Select Image Page
2024-08-21 20:12:38 -07:00
![](/img/webpage.jpg)
### Hardware
- ESP32-S3 N8R2 Dual Type-C
2024-08-22 19:20:45 -07:00
![](/img/esp32s3.jpg)
2024-08-21 20:12:38 -07:00
- Pinout
![](/img/esp32-s3_devkitc-1_pinlayout_v1.1.jpg)