Initial push

This commit is contained in:
ti_mo
2025-12-22 02:32:18 +01:00
commit b313720c81
3 changed files with 617 additions and 0 deletions
+161
View File
@@ -0,0 +1,161 @@
import tkinter as tk
import serial
import serial.tools.list_ports
import threading
from PIL import Image, ImageTk
from io import BytesIO
import queue
def connect() -> serial.Serial:
while True:
ports: list = serial.tools.list_ports.comports()
for i, port in enumerate(ports):
print(f"{i}: {port.device} - {port.description}")
selected_port: str = input("Which port to use?\n")
if selected_port.__len__() < 1: continue
if(not "0123456789".__contains__(selected_port[0])): continue
selected_port_idx: int = int(selected_port[0])
if selected_port_idx >= ports.__len__() or selected_port_idx < 0: continue
s: serial.Serial = serial.Serial(port=ports[selected_port_idx].device, baudrate=921600, timeout=1)
try_counter: int = 0
while not s.is_open:
if try_counter == 10:
print("Failed to open port for 10 times -> stopping thread")
return
try:
s.open()
except:
print("Opening port failed!")
print(f"Try {try_counter}")
try_counter += 1
print(f"Connected successfully! ({ports[selected_port_idx].device})")
try:
s.reset_input_buffer()
except Exception: pass
return s
def receiver_thread(s: serial.Serial, kill_thread_event: threading.Event, image_queue: queue.Queue) -> None:
buf: bytearray = bytearray()
delimiter: str = b"\x04\r\n"
while s.is_open and not kill_thread_event.is_set():
try:
n = s.in_waiting
except Exception:
n = 1
chunk = s.read(n)
if kill_thread_event.is_set(): return
if chunk: buf.extend(chunk)
while True:
idx = buf.find(delimiter)
if idx == -1: break
jpg: bytes = bytes(buf[:idx])
del buf[: idx + delimiter.__len__()]
if not jpg: continue
try:
image_queue.put_nowait(jpg)
except queue.Full:
try:
_ = image_queue.get_nowait()
except queue.Empty: pass
try:
image_queue.put_nowait(jpg)
except queue.Full: pass
if buf.__len__() > 5000000: buf.clear()
def detect_motion(last_image: bytes, new_image: bytes) -> bool:
length1: int = last_image.__len__()
length2: int = new_image.__len__()
length = length1
if length1 < length2:
new_image = new_image[:length1]
elif length2 < length1:
last_image = last_image[:length2]
length = length2
def diff(pixel1, pixel2) -> int:
return pixel1 - pixel2
sum: int = 0
for i in range(length):
sum += diff(last_image[i], new_image[i])
norm = sum / length
ret = norm <= -1 or norm >= 1
# print(f"Norm: {norm}")
return ret
def main():
kill_thread_event: threading.Event = threading.Event()
image_queue: queue.Queue = queue.Queue(maxsize=2)
s: serial.Serial = connect()
receiver = threading.Thread(target=receiver_thread, daemon=True, args=(s, kill_thread_event, image_queue))
receiver.start()
root = tk.Tk()
root.title("ESP32-CAM Stream")
root.geometry("1280x720")
lbl = tk.Label(root)
lbl.pack(fill="both", expand=True)
state = {"photo": None}
last_image: bytes | None = None
def poll_image():
nonlocal last_image
jpg = None
while True:
try:
jpg = image_queue.get_nowait()
except queue.Empty:
break
if jpg is not None:
if last_image is not None and detect_motion(last_image, jpg):
print("Motion detected!")
last_image = jpg
try:
img = Image.open(BytesIO(jpg)).convert("RGB")
w = lbl.winfo_width()
h = lbl.winfo_height()
if w > 1 and h > 1:
img = img.resize((w, h), Image.BILINEAR)
photo = ImageTk.PhotoImage(img)
state["photo"] = photo
lbl.configure(image=photo)
except Exception: pass
root.after(10, poll_image)
def on_close():
kill_thread_event.set()
root.destroy()
print("Closing!")
root.protocol("WM_DELETE_WINDOW", on_close)
poll_image()
root.mainloop()
s.close()
if __name__ == '__main__':
main()