This shows you the differences between two versions of the page.
|
ss:laboratoare:03 [2025/02/25 22:15] jan.vaduva |
ss:laboratoare:03 [2026/03/16 19:36] (current) ciprian.popescu0411 |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ====== Laborator 3: Implementarea unui pipeline CI/CD ====== | + | ====== Laborator 3: Captură și transmisie de imagini prin MQTT cu ESP32-CAM ====== |
| + | |||
| + | În acest laborator ne vom familiariza cu platforma de dezvoltare ESP32-CAM și vom implementa un sistem de supraveghere simplu care transmite imagini către un calculator prin protocolul MQTT. | ||
| ===== Obiective ===== | ===== Obiective ===== | ||
| - | * Construirea și livrarea containerizată a aplicației dezvoltate în laboratoarele anterioare | ||
| - | * Configurarea unui pipeline CI/CD pentru automatizarea procesului de build și deployment | ||
| - | * Implementarea unui sistem de deployment automat în mediu de test/production | ||
| - | ===== Cerințe tehnologice ===== | + | - Înțelegerea arhitecturii unui sistem IoT bazat pe MQTT (Publisher/Subscriber). |
| - | * **Versionare cod**: GitHub/GitLab cu strategii de branching (Git Flow) | + | - Configurarea mediului de dezvoltare (PlatformIO, Mosquitto, Python). |
| - | * **CI/CD Pipeline**: GitHub Actions/GitLab CI/CD/Jenkins | + | - Utilizarea ESP32-CAM pentru captură foto și transmisie Wi-Fi. |
| - | * **Containerizare**: Docker, Docker Compose | + | - Controlul dispozitivului de la distanță prin comenzi MQTT. |
| - | * **Deployment**: Kubernetes (Minikube/MicroK8s) sau Docker Swarm | + | |
| + | ===== Arhitectura sistemului ===== | ||
| + | |||
| + | Sistemul este compus din trei entități principale: | ||
| + | |||
| + | - **ESP32-CAM (Publisher/Subscriber)**: | ||
| + | * Se conectează la rețeaua Wi-Fi. | ||
| + | * Publică imagini (JPEG) pe topicul ''ssproject/images''. | ||
| + | * Ascultă comenzi pe topicul ''ssproject/commands''. | ||
| + | - **Broker MQTT (Mosquitto)**: | ||
| + | * Intermediarul care gestionează mesajele între cameră și calculator. | ||
| + | * Rulează local pe calculatorul vostru. | ||
| + | - **Client Python (Subscriber/Publisher)**: | ||
| + | * Afișează imaginile primite în timp real. | ||
| + | * Trimite comenzi către cameră (''START-LIVE'', ''STOP-LIVE'', ''CAPTURE''). | ||
| + | |||
| + | ===== Configurare mediu de lucru ===== | ||
| + | |||
| + | Pentru a rula proiectul, trebuie să pregătim infrastructura software. | ||
| + | |||
| + | ==== 1. Brokerul MQTT (Mosquitto) ==== | ||
| + | |||
| + | Dacă nu aveți un broker instalat, descărcați [[https://mosquitto.org/download/|Mosquitto]]. | ||
| + | |||
| + | Creați în rădăcina proiectului un fișier de configurare ''mosquitto.conf'' pentru a permite conexiuni externe (de la ESP32): | ||
| + | |||
| + | <file text mosquitto.conf> | ||
| + | listener 1883 | ||
| + | allow_anonymous true | ||
| + | </file> | ||
| + | |||
| + | Porniți brokerul folosind acest fișier de configurare: | ||
| + | |||
| + | <code bash> | ||
| + | # Opriți serviciul default dacă rulează | ||
| + | sudo systemctl stop mosquitto.service | ||
| + | |||
| + | # Porniți brokerul cu configurarea noastră | ||
| + | /usr/sbin/mosquitto -c mosquitto.conf -v | ||
| + | </code> | ||
| + | |||
| + | ==== 2. Clientul Python (Receiver) ==== | ||
| + | |||
| + | Aplicația de vizualizare (''receiver.py'') necesită Python și câteva biblioteci. | ||
| + | |||
| + | <code bash> | ||
| + | # Creați mediul virtual (dacă nu există) | ||
| + | python3 -m venv .venv | ||
| + | source .venv/bin/activate | ||
| + | |||
| + | # Instalați dependențele | ||
| + | pip install paho-mqtt opencv-python numpy | ||
| + | </code> | ||
| + | |||
| + | ==== 3. Crearea proiectului PlatformIO (Firmware ESP32-CAM) ==== | ||
| + | |||
| + | Vom crea un proiect PlatformIO de la zero pentru plăcuța **ESP32-CAM (AI-Thinker)**. | ||
| + | |||
| + | === 3.1 Instalare PlatformIO === | ||
| + | |||
| + | Instalați extensia **PlatformIO IDE** din Visual Studio Code (Extensions → căutați "PlatformIO IDE" → Install). | ||
| + | |||
| + | === 3.2 Crearea proiectului === | ||
| + | |||
| + | Puteți crea proiectul din interfața PlatformIO sau din terminal: | ||
| + | |||
| + | <code bash> | ||
| + | mkdir camera && cd camera | ||
| + | |||
| + | # Creați un mediu virtual Python și instalați PlatformIO CLI | ||
| + | python3 -m venv .venv | ||
| + | source .venv/bin/activate | ||
| + | pip install platformio | ||
| + | |||
| + | # Inițializați proiectul | ||
| + | pio project init --board esp32cam | ||
| + | </code> | ||
| + | |||
| + | === 3.3 Configurarea proiectului === | ||
| + | |||
| + | Înlocuiți conținutul fișierului ''camera/platformio.ini'' cu următorul: | ||
| + | |||
| + | <file ini platformio.ini> | ||
| + | ; PlatformIO Project Configuration File | ||
| + | |||
| + | [env] | ||
| + | platform = espressif32 | ||
| + | framework = arduino | ||
| + | lib_deps = | ||
| + | espressif/esp32-camera@^2.0.4 | ||
| + | WiFi @ ^2.0.0 | ||
| + | knolleary/PubSubClient @ ^2.8 | ||
| + | monitor_speed = 115200 | ||
| + | |||
| + | [env:esp32cam] | ||
| + | board = esp32cam | ||
| + | build_flags = -D CAMERA_MODEL_AI_THINKER | ||
| + | -D MQTT_MAX_PACKET_SIZE=65000 | ||
| + | </file> | ||
| + | |||
| + | **Explicații:** | ||
| + | |||
| + | * ''espressif/esp32-camera'' — biblioteca oficială pentru modulul camerei. | ||
| + | * ''knolleary/PubSubClient'' — client MQTT pentru Arduino. | ||
| + | * ''CAMERA_MODEL_AI_THINKER'' — definește pinii GPIO corespunzători modelului AI-Thinker. | ||
| + | * ''MQTT_MAX_PACKET_SIZE=65000'' — mărește dimensiunea maximă a unui pachet MQTT (necesar pentru trimiterea imaginilor JPEG). | ||
| + | |||
| + | === 3.4 Definirea pinilor GPIO pentru cameră === | ||
| + | |||
| + | Creați fișierul ''camera/include/camera_pins.h'' cu definițiile pinilor GPIO pentru modelul AI-Thinker: | ||
| + | |||
| + | <file c camera_pins.h> | ||
| + | #if defined(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 | ||
| + | |||
| + | #else | ||
| + | #error "Camera model not selected" | ||
| + | #endif | ||
| + | </file> | ||
| + | |||
| + | === 3.5 Structura finală === | ||
| + | |||
| + | După acești pași, structura proiectului ar trebui să arate astfel: | ||
| + | |||
| + | <code text> | ||
| + | camera/ | ||
| + | ├── include/ | ||
| + | │ └── camera_pins.h # Definițiile pinilor GPIO | ||
| + | ├── src/ | ||
| + | │ └── main.cpp # Codul principal (de mai jos) | ||
| + | ├── platformio.ini # Configurarea proiectului | ||
| + | └── .venv/ # Mediul virtual Python | ||
| + | </code> | ||
| + | |||
| + | === 3.6 Compilare și upload === | ||
| + | |||
| + | <code bash> | ||
| + | # Compilare | ||
| + | pio run | ||
| + | |||
| + | # Upload pe placă (asigurați-vă că aveți permisiuni pe portul serial) | ||
| + | sudo chmod 666 /dev/ttyACM0 # sau /dev/ttyUSB0, depinde de placă | ||
| + | pio run -t upload | ||
| + | |||
| + | # Monitor serial (pentru debug) | ||
| + | pio device monitor -b 115200 | ||
| + | </code> | ||
| + | |||
| + | De asemenea, în Visual Studio Code (cu extensia PlatformIO) aceleași acțiuni se pot face și din tastatură: | ||
| + | |||
| + | * **''Ctrl'' + ''Alt'' + ''B''** – build (compilează proiectul) | ||
| + | * **''Ctrl'' + ''Alt'' + ''U''** – upload pe placă | ||
| + | |||
| + | Astfel nu mai este necesar să rulați comenzile manual în terminal. | ||
| + | ===== Codul sursă ===== | ||
| + | |||
| + | ==== Firmware ESP32 (''camera/src/main.cpp'') ==== | ||
| + | |||
| + | Acesta este codul care rulează pe cameră. Analizați-l cu atenție. | ||
| + | |||
| + | <file cpp main.cpp> | ||
| + | /********************************************************************** | ||
| + | Filename : Camera MQTT Client | ||
| + | Description : ESP32-CAM MQTT Image Transfer | ||
| + | **********************************************************************/ | ||
| + | #include "esp_camera.h" | ||
| + | #include <WiFi.h> | ||
| + | #include <PubSubClient.h> | ||
| + | // CAMERA_MODEL is defined in platformio.ini | ||
| + | #include "camera_pins.h" | ||
| + | |||
| + | // =========================== | ||
| + | // Configuration | ||
| + | // =========================== | ||
| + | const char* ssid = ""; // TODO: Modificați cu SSID-ul rețelei voastre | ||
| + | const char* password = ""; // TODO: Modificați cu parola rețelei voastre | ||
| + | const char* mqtt_server = "10.10.10.10"; // TODO: Modificați cu IP-ul calculatorului (ip addr / ipconfig) | ||
| + | const int mqtt_port = 1883; | ||
| + | |||
| + | // Topics | ||
| + | const char* TOPIC_COMMAND = "ssproject/commands"; | ||
| + | const char* TOPIC_IMAGE = "ssproject/images"; | ||
| + | |||
| + | WiFiClient espClient; | ||
| + | PubSubClient client(espClient); | ||
| + | |||
| + | // State variables | ||
| + | bool streaming = false; | ||
| + | bool take_one_picture = false; | ||
| + | unsigned long last_capture_time = 0; | ||
| + | const unsigned long STREAM_INTERVAL = 100; // ms | ||
| + | |||
| + | void setup_camera() { | ||
| + | camera_config_t config = {}; | ||
| + | config.ledc_channel = LEDC_CHANNEL_0; | ||
| + | config.ledc_timer = LEDC_TIMER_0; | ||
| + | config.pin_d0 = Y2_GPIO_NUM; | ||
| + | config.pin_d1 = Y3_GPIO_NUM; | ||
| + | config.pin_d2 = Y4_GPIO_NUM; | ||
| + | config.pin_d3 = Y5_GPIO_NUM; | ||
| + | config.pin_d4 = Y6_GPIO_NUM; | ||
| + | config.pin_d5 = Y7_GPIO_NUM; | ||
| + | config.pin_d6 = Y8_GPIO_NUM; | ||
| + | config.pin_d7 = Y9_GPIO_NUM; | ||
| + | config.pin_xclk = XCLK_GPIO_NUM; | ||
| + | config.pin_pclk = PCLK_GPIO_NUM; | ||
| + | config.pin_vsync = VSYNC_GPIO_NUM; | ||
| + | config.pin_href = HREF_GPIO_NUM; | ||
| + | config.pin_sccb_sda = SIOD_GPIO_NUM; | ||
| + | config.pin_sccb_scl = SIOC_GPIO_NUM; | ||
| + | config.pin_pwdn = PWDN_GPIO_NUM; | ||
| + | config.pin_reset = RESET_GPIO_NUM; | ||
| + | config.xclk_freq_hz = 20000000; | ||
| + | config.pixel_format = PIXFORMAT_JPEG; | ||
| + | |||
| + | if (psramFound()) { | ||
| + | Serial.println("PSRAM found!"); | ||
| + | config.frame_size = FRAMESIZE_VGA; | ||
| + | config.jpeg_quality = 12; | ||
| + | config.fb_count = 2; | ||
| + | } else { | ||
| + | Serial.println("No PSRAM found, using DRAM"); | ||
| + | config.frame_size = FRAMESIZE_SVGA; | ||
| + | config.jpeg_quality = 12; | ||
| + | config.fb_count = 1; | ||
| + | config.fb_location = CAMERA_FB_IN_DRAM; | ||
| + | } | ||
| + | |||
| + | Serial.println("Initializing camera..."); | ||
| + | esp_err_t err = esp_camera_init(&config); | ||
| + | if (err != ESP_OK) { | ||
| + | Serial.printf("Camera init failed with error 0x%x\n", err); | ||
| + | return; | ||
| + | } | ||
| + | Serial.println("Camera Ready!"); | ||
| + | } | ||
| + | |||
| + | void callback(char* topic, byte* payload, unsigned int length) { | ||
| + | Serial.println(">>> CALLBACK FIRED <<<"); | ||
| + | String message; | ||
| + | for (int i = 0; i < length; i++) { | ||
| + | message += (char)payload[i]; | ||
| + | } | ||
| + | Serial.printf("Topic: %s\n", topic); | ||
| + | Serial.printf("Message: [%s] (len=%u)\n", message.c_str(), length); | ||
| + | |||
| + | if (String(topic) == TOPIC_COMMAND) { | ||
| + | if (message == "CAPTURE") { | ||
| + | take_one_picture = true; | ||
| + | Serial.println("=> Action: take_one_picture = true"); | ||
| + | } else if (message == "START-LIVE") { | ||
| + | streaming = true; | ||
| + | Serial.println("=> Action: Streaming Started"); | ||
| + | } else if (message == "STOP-LIVE") { | ||
| + | streaming = false; | ||
| + | Serial.println("=> Action: Streaming Stopped"); | ||
| + | } else { | ||
| + | Serial.println("=> Unknown command, ignoring"); | ||
| + | } | ||
| + | } else { | ||
| + | Serial.println("=> Wrong topic, ignoring"); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | void reconnect() { | ||
| + | while (!client.connected()) { | ||
| + | Serial.print("Attempting MQTT connection..."); | ||
| + | String clientId = "ESP32CamClient-"; | ||
| + | clientId += String(random(0xffff), HEX); | ||
| + | Serial.printf(" (clientId=%s)\n", clientId.c_str()); | ||
| + | |||
| + | if (client.connect(clientId.c_str())) { | ||
| + | Serial.println("MQTT connected!"); | ||
| + | bool subOk = client.subscribe(TOPIC_COMMAND); | ||
| + | Serial.printf("Subscribe to '%s': %s\n", TOPIC_COMMAND, subOk ? "OK" : "FAILED"); | ||
| + | Serial.printf("Buffer size: %d\n", client.getBufferSize()); | ||
| + | Serial.printf("Free heap: %u bytes\n", ESP.getFreeHeap()); | ||
| + | } else { | ||
| + | Serial.print("failed, rc="); | ||
| + | Serial.print(client.state()); | ||
| + | Serial.println(" try again in 5 seconds"); | ||
| + | delay(5000); | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | void setup() { | ||
| + | Serial.begin(115200); | ||
| + | delay(1000); | ||
| + | Serial.println(); | ||
| + | Serial.println("============================"); | ||
| + | Serial.println(" ESP32-CAM MQTT Client"); | ||
| + | Serial.println("============================"); | ||
| + | Serial.printf("Free heap at start: %u bytes\n", ESP.getFreeHeap()); | ||
| + | Serial.printf("PSRAM size: %u bytes\n", ESP.getPsramSize()); | ||
| + | |||
| + | setup_camera(); | ||
| + | |||
| + | Serial.printf("Connecting to WiFi: %s\n", ssid); | ||
| + | WiFi.begin(ssid, password); | ||
| + | while (WiFi.status() != WL_CONNECTED) { | ||
| + | delay(500); | ||
| + | Serial.print("."); | ||
| + | } | ||
| + | Serial.printf("\nWiFi connected! IP: %s\n", WiFi.localIP().toString().c_str()); | ||
| + | |||
| + | client.setServer(mqtt_server, mqtt_port); | ||
| + | client.setCallback(callback); | ||
| + | client.setBufferSize(65000); | ||
| + | } | ||
| + | |||
| + | void captureAndPublish() { | ||
| + | camera_fb_t * fb = esp_camera_fb_get(); | ||
| + | if (!fb) { | ||
| + | Serial.println("Camera capture failed"); | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | if (client.publish(TOPIC_IMAGE, (const uint8_t*)fb->buf, fb->len)) { | ||
| + | Serial.printf("Image published: %u bytes\n", fb->len); | ||
| + | } else { | ||
| + | Serial.println("Publish failed"); | ||
| + | } | ||
| + | |||
| + | esp_camera_fb_return(fb); | ||
| + | } | ||
| + | |||
| + | unsigned long last_heartbeat = 0; | ||
| + | |||
| + | void loop() { | ||
| + | if (!client.connected()) { | ||
| + | Serial.println("MQTT disconnected, reconnecting..."); | ||
| + | reconnect(); | ||
| + | } | ||
| + | client.loop(); | ||
| + | |||
| + | unsigned long now = millis(); | ||
| + | |||
| + | // Print a heartbeat every 5 seconds so you know the loop is running | ||
| + | if (now - last_heartbeat > 5000) { | ||
| + | Serial.printf("[heartbeat] millis=%lu connected=%d streaming=%d free_heap=%u\n", | ||
| + | now, client.connected(), streaming, ESP.getFreeHeap()); | ||
| + | last_heartbeat = now; | ||
| + | } | ||
| + | |||
| + | if (take_one_picture) { | ||
| + | Serial.println("Taking single picture..."); | ||
| + | captureAndPublish(); | ||
| + | take_one_picture = false; | ||
| + | } | ||
| + | |||
| + | if (streaming && (now - last_capture_time > STREAM_INTERVAL)) { | ||
| + | captureAndPublish(); | ||
| + | last_capture_time = now; | ||
| + | } | ||
| + | } | ||
| + | </file> | ||
| + | |||
| + | <note warning>Nu deschideți ''Serial Monitor'' în Visual Studio Code, deoarece este foarte posibil să-și dea ''RESET'' placa.</note> | ||
| + | |||
| + | ==== Receiver Python (''receiver/receiver.py'') ==== | ||
| + | |||
| + | Acesta este clientul care rulează pe calculator, primește imaginile și trimite comenzi. | ||
| + | |||
| + | <file python receiver.py> | ||
| + | import paho.mqtt.client as mqtt | ||
| + | import cv2 | ||
| + | import numpy as np | ||
| + | import threading | ||
| + | |||
| + | # Configuration | ||
| + | BROKER = "192.168.50.239" # TODO: Modificați cu IP-ul brokerului vostru | ||
| + | PORT = 1883 | ||
| + | TOPIC_IMAGE = "ssproject/images" | ||
| + | TOPIC_COMMAND = "ssproject/commands" | ||
| + | |||
| + | # Global flags | ||
| + | running = True | ||
| + | |||
| + | # Shared frame storage (thread-safe via lock) | ||
| + | latest_frame = None | ||
| + | frame_lock = threading.Lock() | ||
| + | |||
| + | def on_connect(client, userdata, flags, rc): | ||
| + | if rc == 0: | ||
| + | print("Connected to MQTT Broker!") | ||
| + | client.subscribe(TOPIC_IMAGE) | ||
| + | else: | ||
| + | print(f"Failed to connect, return code {rc}") | ||
| + | |||
| + | def on_message(client, userdata, msg): | ||
| + | global latest_frame | ||
| + | try: | ||
| + | nparr = np.frombuffer(msg.payload, np.uint8) | ||
| + | img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) | ||
| + | |||
| + | if img is not None: | ||
| + | # Store the frame — do NOT call cv2.imshow here (wrong thread!) | ||
| + | with frame_lock: | ||
| + | latest_frame = img | ||
| + | else: | ||
| + | print("Failed to decode image") | ||
| + | except Exception as e: | ||
| + | print(f"Error processing image: {e}") | ||
| + | |||
| + | def main(): | ||
| + | global running, latest_frame | ||
| + | client = mqtt.Client() | ||
| + | client.on_connect = on_connect | ||
| + | client.on_message = on_message | ||
| + | |||
| + | try: | ||
| + | client.connect(BROKER, PORT, 60) | ||
| + | client.loop_start() | ||
| + | |||
| + | print("\n--- ESP32 Camera Controller ---") | ||
| + | print("Controls:") | ||
| + | print(" 's' : Take Single Picture") | ||
| + | print(" 'b' : Begin Stream") | ||
| + | print(" 'e' : End Stream") | ||
| + | print(" 'q' : Quit") | ||
| + | print("-------------------------------\n") | ||
| + | |||
| + | cv2.namedWindow("ESP32-CAM Stream", cv2.WINDOW_AUTOSIZE) | ||
| + | |||
| + | while running: | ||
| + | # Display the latest frame if available (main thread only) | ||
| + | with frame_lock: | ||
| + | frame = latest_frame | ||
| + | |||
| + | if frame is not None: | ||
| + | cv2.imshow("ESP32-CAM Stream", frame) | ||
| + | |||
| + | # Handle GUI events and keyboard input (main thread only) | ||
| + | key = cv2.waitKey(30) & 0xFF | ||
| + | |||
| + | if key == ord('q'): | ||
| + | running = False | ||
| + | elif key == ord('s'): | ||
| + | print("Command: Capture") | ||
| + | client.publish(TOPIC_COMMAND, "CAPTURE") | ||
| + | elif key == ord('b'): | ||
| + | print("Command: Start Stream") | ||
| + | client.publish(TOPIC_COMMAND, "START-LIVE") | ||
| + | elif key == ord('e'): | ||
| + | print("Command: Stop Stream") | ||
| + | client.publish(TOPIC_COMMAND, "STOP-LIVE") | ||
| + | |||
| + | except KeyboardInterrupt: | ||
| + | print("\nExiting...") | ||
| + | except Exception as e: | ||
| + | print(f"Error: {e}") | ||
| + | finally: | ||
| + | client.loop_stop() | ||
| + | client.disconnect() | ||
| + | cv2.destroyAllWindows() | ||
| + | |||
| + | if __name__ == "__main__": | ||
| + | main() | ||
| + | </file> | ||
| + | |||
| + | ===== Structura codului ===== | ||
| + | |||
| + | ==== Firmware (''main.cpp'') ==== | ||
| + | |||
| + | Codul de pe cameră îndeplinește următoarele funcții: | ||
| + | |||
| + | * **''setup_camera()''**: Inițializează senzorul OV2640. | ||
| + | * **''callback()''**: Funcția apelată când se primește un mesaj pe ''ssproject/commands''. Interpretează: | ||
| + | * ''CAPTURE'' -> Face o poză. | ||
| + | * ''START-LIVE'' -> Activează flag-ul ''streaming''. | ||
| + | * ''STOP-LIVE'' -> Dezactivează flag-ul ''streaming''. | ||
| + | * **''loop()''**: Verifică starea ''streaming''. Dacă este activ, capturează și trimite o imagine la fiecare 100ms. | ||
| + | |||
| + | ==== Configurare Wi-Fi și MQTT ==== | ||
| + | |||
| + | În ''main.cpp'', trebuie să modificați următoarele constante pentru a corespunde rețelei voastre: | ||
| + | |||
| + | <code cpp> | ||
| + | const char* ssid = "NUME_RETEA"; | ||
| + | const char* password = "PAROLA_RETEA"; | ||
| + | const char* mqtt_server = "IP_CALCULATOR"; // Rulați `ip addr` sau `ipconfig` pentru a-l afla | ||
| + | </code> | ||
| - | ===== Funcționalități ===== | + | ==== Receiver (''receiver.py'') ==== |
| - | ==== 1. Construirea și gestionarea containerelor Docker ==== | + | Scriptul Python se conectează la broker și afișează imaginile folosind OpenCV. De asemenea, ascultă tastatura pentru a trimite comenzi: |
| - | * Crearea unui Dockerfile pentru aplicație | + | |
| - | * Configurarea unui sistem de build și push automat către un registry (Docker Hub, GitHub Container Registry) | + | |
| - | * Utilizarea Docker Compose pentru rularea serviciilor local | + | |
| - | ==== 2. Implementarea pipeline-ului CI/CD ==== | + | * **''b''**: Begin Stream (''START-LIVE'') |
| - | * Definirea workflow-ului CI/CD (build, push, deploy) | + | * **''e''**: End Stream (''STOP-LIVE'') |
| - | * Configurarea unui sistem de build automat la fiecare commit sau pull request | + | * **''s''**: Single Capture (''CAPTURE'') |
| - | * Automatizarea livrării containerului către registry | + | * **''q''**: Quit |
| - | ==== 3. Deployment automatizat ==== | + | ===== Desfășurarea laboratorului ===== |
| - | * Implementarea unui sistem de deploy automat în mediu de testare | + | |
| - | * Configurarea unui mediu de staging/producție folosind Kubernetes/Docker Swarm | + | |
| - | * Rollback automat în caz de eșec al deployment-ului | + | |
| - | ===== Evaluare ===== | + | - **Configurare rețea**: Deschideți ''camera/src/main.cpp'' și setați ''ssid'', ''password'' și ''mqtt_server'' (IP-ul PC-ului vostru). |
| - | * Construirea și livrarea aplicației în containere Docker (40%) | + | - **Pornire broker**: Rulați comanda de pornire Mosquitto într-un terminal. |
| - | * Configurarea și rularea pipeline-ului CI/CD (30%) | + | - **Pornire receiver**: Rulați ''python receiver.py'' într-un alt terminal. |
| - | * Deployment automatizat în mediu de test/producție (30%) | + | - **Upload**: |
| + | * Conectați ESP32-CAM la USB (prin programator, dacă e cazul). | ||
| + | * Asigurați-vă că aveți permisiuni pe portul serial: ''sudo chmod 666 /dev/ttyACM0''. | ||
| + | * Folosiți comanda ''Upload'' din PlatformIO sau terminal: ''pio run -t upload''. | ||
| + | - **Testare**: | ||
| + | * După resetare, camera se va conecta la Wi-Fi. | ||
| + | * Din fereastra receiver-ului, apăsați ''b'' pentru a porni live stream-ul. | ||
| + | * Verificați latența și calitatea imaginii. | ||
| - | ===== Resurse suplimentare ===== | + | <note tip> |
| - | * [https://docs.github.com/en/actions GitHub Actions Documentation] / [https://docs.gitlab.com/ee/ci GitLab CI/CD Documentation] | + | Dacă ați configurat totul corect (broker-ul este pornit, camera publică pe ''ssproject/images'', și receiver-ul sau aplicația web este conectată la același broker), ar trebui să puteți vizualiza stream-ul și prin intermediul **interfaței web** a aplicației (serverul web care face subscribe la același topic MQTT). Aceasta confirmă faptul că sistemul funcționează end-to-end. |
| - | * [https://www.docker.com Docker Documentation] | + | </note> |
| - | * [https://kubernetes.io/docs Kubernetes Documentation] | + | |
| + | ===== Întrebări și exerciții ===== | ||
| + | - Ce se întâmplă dacă modificați ''STREAM_INTERVAL'' în ''main.cpp'' la o valoare mai mică (ex: 20ms)? Cum afectează asta calitatea imaginii și latența? | ||
| + | - Analizați funcția ''callback'' din ''main.cpp''. Adăugați o comandă nouă ''FLASH-ON'' care să aprindă LED-ul flash al camerei (GPIO 4). | ||