amc:ss2025:group-a:start
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| amc:ss2025:group-a:start [2025/07/29 09:36] – [System Configuration and Setup] 35120_students.hsrw | amc:ss2025:group-a:start [2025/07/29 15:11] (current) – [Data analysis] 35120_students.hsrw | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | ====== Gießwagen - Plant Detection ====== | ||
| + | |||
| + | By Dylan Elian Huete Arbizu (35120) | ||
| ==== Introduction ==== | ==== Introduction ==== | ||
| Line 31: | Line 34: | ||
| * Wi-Fi Network: For ESP32 to connect and transmit data. | * Wi-Fi Network: For ESP32 to connect and transmit data. | ||
| - | ===== Pin Assignments | + | ==== Pin Assignments ==== |
| ^ Function | ^ Function | ||
| Line 143: | Line 146: | ||
| * All configuration parameters (SSID, pins, thresholds) are user-adjustable. | * All configuration parameters (SSID, pins, thresholds) are user-adjustable. | ||
| + | ==== Complete ESP32 code ==== | ||
| + | <code c> | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include " | ||
| + | #include " | ||
| + | #include " | ||
| + | #include " | ||
| + | #include " | ||
| + | #include " | ||
| + | #include " | ||
| + | #include " | ||
| + | #include " | ||
| + | #include " | ||
| + | #include " | ||
| + | #include " | ||
| + | #include " | ||
| + | #include " | ||
| + | #include " | ||
| + | |||
| + | #define OUT_GPIO GPIO_NUM_7 | ||
| + | #define BUTTON_PIN 4 | ||
| + | #define DEBOUNCE_uS 100000 | ||
| + | |||
| + | |||
| + | #define EXAMPLE_ESP_WIFI_SSID | ||
| + | #define EXAMPLE_ESP_WIFI_PASS | ||
| + | #define EXAMPLE_ESP_MAXIMUM_RETRY | ||
| + | |||
| + | #define TCP_PORT 5055 | ||
| + | #define FRAME_SIZE 64 | ||
| + | |||
| + | static EventGroupHandle_t s_wifi_event_group; | ||
| + | #define WIFI_CONNECTED_BIT BIT0 | ||
| + | #define WIFI_FAIL_BIT | ||
| + | |||
| + | static const char *TAG = "wifi station"; | ||
| + | static int s_retry_num = 0; | ||
| + | |||
| + | // TCP server global socket | ||
| + | static int client_sock = -1; | ||
| + | static int server_sock = -1; | ||
| + | static struct sockaddr_in client_addr; | ||
| + | static socklen_t client_addr_len = sizeof(client_addr); | ||
| + | |||
| + | // VL53L8CX variables | ||
| + | VL53L8CX_Configuration | ||
| + | VL53L8CX_ResultsData | ||
| + | esp_err_t ret; | ||
| + | uint16_t frame[FRAME_SIZE]; | ||
| + | uint16_t mark[FRAME_SIZE] = { [0 ... 63] = 0 }; | ||
| + | |||
| + | |||
| + | // GPTimer handle | ||
| + | static gptimer_handle_t gptimer = NULL; | ||
| + | |||
| + | int compare(const void *a, const void *b) { | ||
| + | return (*(int*)a - *(int*)b); | ||
| + | } | ||
| + | |||
| + | // Returns median of a 64-element array | ||
| + | int median(int *data) { | ||
| + | int tmp[64]; | ||
| + | memcpy(tmp, data, sizeof(tmp)); | ||
| + | qsort(tmp, 64, sizeof(int), | ||
| + | return tmp[31]; // 32nd element is the median | ||
| + | } | ||
| + | |||
| + | // Converts 1D index to row, col for 8x8 | ||
| + | void idx_to_rowcol(int idx, int *row, int *col) { | ||
| + | *row = idx / 8; | ||
| + | *col = idx % 8; | ||
| + | } | ||
| + | |||
| + | // weight: background_distance - value (if object), else 0.0 | ||
| + | float calculate_weight(int value, int bg, int offset, bool *object) { | ||
| + | if (value < bg - offset) { | ||
| + | *object = true; | ||
| + | return (float)(bg - value); | ||
| + | } else { | ||
| + | *object = false; | ||
| + | return 0.0f; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // Find object centroid from 1D (row-major) 64-element array | ||
| + | bool find_object_center( | ||
| + | int *distance, | ||
| + | int offset, | ||
| + | float *y_c, // output: centroid y (0-7, row) | ||
| + | float *x_c, // output: centroid x (0-7, col) | ||
| + | bool *object_mask | ||
| + | ) { | ||
| + | // 1. Find background | ||
| + | int bg = median(distance); | ||
| + | |||
| + | // 2. Initialize and calculate weights and mask | ||
| + | float weights[64] = {0.0f}; | ||
| + | bool any_object = false; | ||
| + | |||
| + | for (int i = 0; i < 64; ++i) { | ||
| + | object_mask[i] = false; | ||
| + | weights[i] = calculate_weight(distance[i], | ||
| + | if (object_mask[i]) any_object = true; | ||
| + | } | ||
| + | |||
| + | if (!any_object) { | ||
| + | return false; // no object detected | ||
| + | } | ||
| + | |||
| + | // 3. Calculate weighted centroid | ||
| + | float sum_y = 0.0f, sum_x = 0.0f, sum_w = 0.0f; | ||
| + | for (int i = 0; i < 64; ++i) { | ||
| + | if (weights[i] > 0.0f) { | ||
| + | int row, col; | ||
| + | idx_to_rowcol(i, | ||
| + | sum_y += (float)row * weights[i]; | ||
| + | sum_x += (float)col * weights[i]; | ||
| + | sum_w += weights[i]; | ||
| + | } | ||
| + | } | ||
| + | *y_c = sum_y / sum_w; | ||
| + | *x_c = sum_x / sum_w; | ||
| + | |||
| + | return true; // object detected, centroid calculated | ||
| + | } | ||
| + | |||
| + | // --- Wi-Fi event handler | ||
| + | static void event_handler(void* arg, esp_event_base_t event_base, | ||
| + | int32_t event_id, void* event_data) | ||
| + | { | ||
| + | if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { | ||
| + | esp_wifi_connect(); | ||
| + | } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { | ||
| + | if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) { | ||
| + | esp_wifi_connect(); | ||
| + | s_retry_num++; | ||
| + | ESP_LOGI(TAG, | ||
| + | } else { | ||
| + | xEventGroupSetBits(s_wifi_event_group, | ||
| + | } | ||
| + | ESP_LOGI(TAG," | ||
| + | } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { | ||
| + | ip_event_got_ip_t* event = event_data; | ||
| + | ESP_LOGI(TAG, | ||
| + | s_retry_num = 0; | ||
| + | xEventGroupSetBits(s_wifi_event_group, | ||
| + | } | ||
| + | } | ||
| + | |||
| + | void wifi_init_sta(void) | ||
| + | { | ||
| + | s_wifi_event_group = xEventGroupCreate(); | ||
| + | ESP_ERROR_CHECK(esp_netif_init()); | ||
| + | ESP_ERROR_CHECK(esp_event_loop_create_default()); | ||
| + | esp_netif_create_default_wifi_sta(); | ||
| + | |||
| + | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); | ||
| + | ESP_ERROR_CHECK(esp_wifi_init(& | ||
| + | |||
| + | esp_event_handler_instance_t instance_any_id; | ||
| + | esp_event_handler_instance_t instance_got_ip; | ||
| + | ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, | ||
| + | ESP_EVENT_ANY_ID, | ||
| + | & | ||
| + | NULL, | ||
| + | & | ||
| + | ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, | ||
| + | IP_EVENT_STA_GOT_IP, | ||
| + | & | ||
| + | NULL, | ||
| + | & | ||
| + | |||
| + | wifi_config_t wifi_config = { | ||
| + | .sta = { | ||
| + | .ssid = EXAMPLE_ESP_WIFI_SSID, | ||
| + | .password = EXAMPLE_ESP_WIFI_PASS, | ||
| + | .threshold.authmode = WIFI_AUTH_WPA2_PSK, | ||
| + | }, | ||
| + | }; | ||
| + | ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); | ||
| + | ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, | ||
| + | ESP_ERROR_CHECK(esp_wifi_start() ); | ||
| + | |||
| + | ESP_LOGI(TAG, | ||
| + | |||
| + | EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, | ||
| + | WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, | ||
| + | pdFALSE, | ||
| + | pdFALSE, | ||
| + | portMAX_DELAY); | ||
| + | |||
| + | if (bits & WIFI_CONNECTED_BIT) { | ||
| + | ESP_LOGI(TAG, | ||
| + | | ||
| + | } else if (bits & WIFI_FAIL_BIT) { | ||
| + | ESP_LOGI(TAG, | ||
| + | | ||
| + | } else { | ||
| + | ESP_LOGE(TAG, | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // --- GPTimer Setup --- | ||
| + | void gptimer_setup(void) | ||
| + | { | ||
| + | gptimer_config_t timer_config = { | ||
| + | .clk_src = GPTIMER_CLK_SRC_DEFAULT, | ||
| + | .direction = GPTIMER_COUNT_UP, | ||
| + | .resolution_hz = 1000000, // 1 MHz = 1 tick per microsecond | ||
| + | }; | ||
| + | ESP_ERROR_CHECK(gptimer_new_timer(& | ||
| + | ESP_ERROR_CHECK(gptimer_enable(gptimer)); | ||
| + | ESP_ERROR_CHECK(gptimer_start(gptimer)); | ||
| + | } | ||
| + | |||
| + | // --- TCP Server Task --- | ||
| + | void tcp_server_task(void *pvParameters) | ||
| + | { | ||
| + | struct sockaddr_in server_addr; | ||
| + | |||
| + | server_sock = socket(AF_INET, | ||
| + | if (server_sock < 0) { | ||
| + | ESP_LOGE(" | ||
| + | vTaskDelete(NULL); | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | int opt = 1; | ||
| + | setsockopt(server_sock, | ||
| + | |||
| + | server_addr.sin_family = AF_INET; | ||
| + | server_addr.sin_addr.s_addr = htonl(INADDR_ANY); | ||
| + | server_addr.sin_port = htons(TCP_PORT); | ||
| + | |||
| + | if (bind(server_sock, | ||
| + | ESP_LOGE(" | ||
| + | close(server_sock); | ||
| + | vTaskDelete(NULL); | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | if (listen(server_sock, | ||
| + | ESP_LOGE(" | ||
| + | close(server_sock); | ||
| + | vTaskDelete(NULL); | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | ESP_LOGI(" | ||
| + | |||
| + | while (1) { | ||
| + | ESP_LOGI(" | ||
| + | client_sock = accept(server_sock, | ||
| + | if (client_sock < 0) { | ||
| + | ESP_LOGE(" | ||
| + | continue; | ||
| + | } | ||
| + | ESP_LOGI(" | ||
| + | // Block here until client disconnects; | ||
| + | while (1) { | ||
| + | char buf[8]; | ||
| + | int len = recv(client_sock, | ||
| + | if (len == 0) { | ||
| + | ESP_LOGI(" | ||
| + | close(client_sock); | ||
| + | client_sock = -1; | ||
| + | break; | ||
| + | } | ||
| + | vTaskDelay(pdMS_TO_TICKS(100)); | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // --- VL53L8CX Task --- | ||
| + | void vl53l8cx_task(void *pvParameters) { | ||
| + | uint8_t isReady; | ||
| + | bool nozzel_state = true; | ||
| + | while (1) { | ||
| + | ret = vl53l8cx_check_data_ready(& | ||
| + | if (ret == ESP_OK && isReady) { | ||
| + | vl53l8cx_get_ranging_data(& | ||
| + | for (int i = 0; i < FRAME_SIZE; ++i) { | ||
| + | frame[i] = results.distance_mm[VL53L8CX_NB_TARGET_PER_ZONE*i]; | ||
| + | } | ||
| + | // Get timestamp from GPTimer | ||
| + | uint64_t timestamp = 0; | ||
| + | gptimer_get_raw_count(gptimer, | ||
| + | // Prepare buffer: [timestamp][frame] | ||
| + | uint8_t sendbuf[8 + FRAME_SIZE * 2]; | ||
| + | memcpy(sendbuf, | ||
| + | memcpy(sendbuf + 8, frame, FRAME_SIZE * 2); | ||
| + | if(frame[35] <= 300 && frame[36] <= 300 && frame[43] <= 300 && frame[44] <= 300 && nozzel_state){ | ||
| + | gpio_set_level(OUT_GPIO, | ||
| + | ESP_LOGI(" | ||
| + | nozzel_state = false; | ||
| + | | ||
| + | }else if(frame[35] >= 300 && frame[36] >= 300 && frame[43] >= 300 && frame[44] >= 300 && !nozzel_state){ | ||
| + | gpio_set_level(OUT_GPIO, | ||
| + | ESP_LOGI(" | ||
| + | nozzel_state = true; | ||
| + | } | ||
| + | // Send to TCP client if connected | ||
| + | if (client_sock >= 0) { | ||
| + | int to_send = sizeof(sendbuf); | ||
| + | int sent = send(client_sock, | ||
| + | if (sent < 0) { | ||
| + | ESP_LOGE(" | ||
| + | close(client_sock); | ||
| + | client_sock = -1; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | } | ||
| + | // | ||
| + | } | ||
| + | } | ||
| + | |||
| + | void config_gpio(){ | ||
| + | gpio_config_t io_conf = { | ||
| + | .pin_bit_mask = (1ULL << BUTTON_PIN), | ||
| + | .mode = GPIO_MODE_INPUT, | ||
| + | .intr_type = GPIO_INTR_NEGEDGE, | ||
| + | .pull_up_en = 1 | ||
| + | }; | ||
| + | gpio_config(& | ||
| + | gpio_config_t out_conf = { | ||
| + | .pin_bit_mask = (1ULL << OUT_GPIO), | ||
| + | .mode = GPIO_MODE_OUTPUT, | ||
| + | }; | ||
| + | gpio_config(& | ||
| + | } | ||
| + | static QueueHandle_t interr_queue = NULL; | ||
| + | |||
| + | void IRAM_ATTR interr_handler(void* arg) { | ||
| + | uint32_t pin = (uint32_t) arg; | ||
| + | xQueueSendFromISR(interr_queue, | ||
| + | } | ||
| + | |||
| + | static uint64_t last_change = 0; | ||
| + | static int last_state = 1; // Asssuming pull-up: 1 = no pressed, 0 = pressed | ||
| + | |||
| + | void task_pin_reading(void* params) { | ||
| + | uint32_t pin_received; | ||
| + | while (1) { | ||
| + | if (xQueueReceive(interr_queue, | ||
| + | // Read pin state | ||
| + | int current_state = gpio_get_level(pin_received); | ||
| + | // Get timestamp from GPTimer | ||
| + | uint64_t timestamp = 0; | ||
| + | gptimer_get_raw_count(gptimer, | ||
| + | // If the state has changed and enough time has passed | ||
| + | if (current_state == 0 && last_state == 1 && (timestamp - last_change) >= DEBOUNCE_uS) { | ||
| + | last_state = 0; | ||
| + | last_change = timestamp; | ||
| + | // Prepare buffer: [timestamp][frame] | ||
| + | uint8_t sendbuf[8 + FRAME_SIZE * 2]; | ||
| + | memcpy(sendbuf, | ||
| + | memcpy(sendbuf + 8, mark, FRAME_SIZE * 2); | ||
| + | // Send to TCP client if connected | ||
| + | if (client_sock >= 0) { | ||
| + | int to_send = sizeof(sendbuf); | ||
| + | int sent = send(client_sock, | ||
| + | if (sent < 0) { | ||
| + | ESP_LOGE(" | ||
| + | close(client_sock); | ||
| + | client_sock = -1; | ||
| + | } | ||
| + | } | ||
| + | }else if(current_state == 1 && last_state == 0){ | ||
| + | last_state = 1; | ||
| + | last_change = timestamp; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | |||
| + | void app_main(void) | ||
| + | { | ||
| + | config_gpio(); | ||
| + | | ||
| + | // create queue for 10 events | ||
| + | interr_queue = xQueueCreate(10, | ||
| + | | ||
| + | // install service for ISR | ||
| + | gpio_install_isr_service(0); | ||
| + | gpio_isr_handler_add(BUTTON_PIN, | ||
| + | |||
| + | // | ||
| + | esp_err_t ret = nvs_flash_init(); | ||
| + | if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { | ||
| + | ESP_ERROR_CHECK(nvs_flash_erase()); | ||
| + | ret = nvs_flash_init(); | ||
| + | } | ||
| + | ESP_ERROR_CHECK(ret); | ||
| + | |||
| + | ESP_LOGI(TAG, | ||
| + | wifi_init_sta(); | ||
| + | |||
| + | // Setup GPTimer | ||
| + | gptimer_setup(); | ||
| + | |||
| + | //Define the i2c bus configuration | ||
| + | i2c_port_t i2c_port = I2C_NUM_1; | ||
| + | i2c_master_bus_config_t i2c_mst_config = { | ||
| + | .clk_source = I2C_CLK_SRC_DEFAULT, | ||
| + | .i2c_port = i2c_port, | ||
| + | .scl_io_num = 9, | ||
| + | .sda_io_num = 8, | ||
| + | .glitch_ignore_cnt = 7, | ||
| + | .flags.enable_internal_pullup = true, | ||
| + | }; | ||
| + | |||
| + | i2c_master_bus_handle_t bus_handle; | ||
| + | ESP_ERROR_CHECK(i2c_new_master_bus(& | ||
| + | |||
| + | //Define the i2c device configuration | ||
| + | i2c_device_config_t dev_cfg = { | ||
| + | .dev_addr_length = I2C_ADDR_BIT_LEN_7, | ||
| + | .device_address = VL53L8CX_DEFAULT_I2C_ADDRESS >> 1, | ||
| + | .scl_speed_hz = VL53L8CX_MAX_CLK_SPEED, | ||
| + | }; | ||
| + | |||
| + | Dev.platform.bus_config = i2c_mst_config; | ||
| + | i2c_master_bus_add_device(bus_handle, | ||
| + | |||
| + | Dev.platform.reset_gpio = GPIO_NUM_5; | ||
| + | VL53L8CX_Reset_Sensor(& | ||
| + | |||
| + | uint8_t isAlive = 0; | ||
| + | ret = vl53l8cx_is_alive(& | ||
| + | if(!isAlive || ret != ESP_OK) | ||
| + | { | ||
| + | printf(" | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | ret = vl53l8cx_init(& | ||
| + | if (ret != ESP_OK) { | ||
| + | printf(" | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | ret = vl53l8cx_set_resolution(& | ||
| + | if (ret != ESP_OK) { | ||
| + | printf(" | ||
| + | return; | ||
| + | } | ||
| + | printf(" | ||
| + | |||
| + | ret = vl53l8cx_set_ranging_frequency_hz(& | ||
| + | if (ret != ESP_OK) { | ||
| + | printf(" | ||
| + | return; | ||
| + | } | ||
| + | ret = vl53l8cx_set_sharpener_percent(& | ||
| + | if (ret != ESP_OK) { | ||
| + | printf(" | ||
| + | return; | ||
| + | } | ||
| + | ret = vl53l8cx_set_target_order(& | ||
| + | if (ret != ESP_OK) { | ||
| + | printf(" | ||
| + | return; | ||
| + | } | ||
| + | ret = vl53l8cx_start_ranging(& | ||
| + | if (ret != ESP_OK) { | ||
| + | printf(" | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | // Start TCP server and sensor tasks | ||
| + | xTaskCreate(tcp_server_task, | ||
| + | xTaskCreate(vl53l8cx_task, | ||
| + | xTaskCreate(task_pin_reading, | ||
| + | } | ||
| + | |||
| + | </ | ||
| ==== Assembly ==== | ==== Assembly ==== | ||
| Line 156: | Line 640: | ||
| * Connect a client to ESP32' | * Connect a client to ESP32' | ||
| + | ==== Circuit diagram ==== | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | ==== Client Software for Data Reception and Visualization ==== | ||
| + | A fully functional Python client application logs incoming data and visualizes it as a heatmap in real time: | ||
| + | |||
| + | * Raw Data Reception: Receives packets of 136 bytes each (64 x 2-byte sensor readings + 8 bytes timestamp) from the ESP32 over a TCP socket. | ||
| + | |||
| + | * Data Logging: Writes each received frame with microsecond-precision timestamps to a CSV file for later analysis. | ||
| + | |||
| + | * Live Visualization: | ||
| + | |||
| + | * Threading/ | ||
| + | |||
| + | * Safe Shutdown: Ensures sockets and files are properly closed when the application exits. | ||
| + | |||
| + | <code python> | ||
| + | import socket | ||
| + | import struct | ||
| + | import csv | ||
| + | import tkinter as tk | ||
| + | from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg | ||
| + | import matplotlib.pyplot as plt | ||
| + | import numpy as np | ||
| + | from scipy.ndimage import zoom | ||
| + | import threading | ||
| + | |||
| + | ESP32_IP = " | ||
| + | ESP32_PORT = 5055 | ||
| + | CSV_FILENAME = " | ||
| + | FRAME_SIZE = 8 + 64 * 2 # 8 bytes timestamp + 128 bytes frame data | ||
| + | UPSCALE_FACTOR = 8 # For smooth 8x8 -> 64x64 heatmap | ||
| + | |||
| + | latest_matrix = None | ||
| + | latest_timestamp = None | ||
| + | lock = threading.Lock() | ||
| + | |||
| + | def re_range(pMatrix): | ||
| + | pMatrix = np.array(pMatrix) | ||
| + | nMax = pMatrix.max() | ||
| + | rat = nMax / 5000 if nMax != 0 else 1 | ||
| + | r_matrix = pMatrix / rat | ||
| + | return r_matrix.astype(int) | ||
| + | |||
| + | def data_receiver(sock, | ||
| + | global latest_matrix, | ||
| + | while True: | ||
| + | data = b'' | ||
| + | while len(data) < FRAME_SIZE: | ||
| + | try: | ||
| + | packet = sock.recv(FRAME_SIZE - len(data)) | ||
| + | except socket.timeout: | ||
| + | continue | ||
| + | if not packet: | ||
| + | print(" | ||
| + | return | ||
| + | data += packet | ||
| + | timestamp_us = struct.unpack('< | ||
| + | distances = struct.unpack('< | ||
| + | matrix = np.array(distances, | ||
| + | writer.writerow([timestamp_us] + list(matrix.flatten())) | ||
| + | csvfile.flush() | ||
| + | with lock: | ||
| + | latest_matrix = matrix | ||
| + | latest_timestamp = timestamp_us | ||
| + | |||
| + | def update_gui(): | ||
| + | with lock: | ||
| + | matrix = None if latest_matrix is None else latest_matrix.copy() | ||
| + | timestamp = latest_timestamp | ||
| + | if matrix is not None: | ||
| + | matrix = re_range(matrix) | ||
| + | high_res_matrix = zoom(matrix, | ||
| + | im.set_array(high_res_matrix) | ||
| + | ax.set_title(f" | ||
| + | canvas.draw() | ||
| + | root.after(100, | ||
| + | |||
| + | def clean_exit(): | ||
| + | global running | ||
| + | running = False | ||
| + | try: | ||
| + | sock.close() | ||
| + | except Exception: | ||
| + | pass | ||
| + | try: | ||
| + | csvfile.close() | ||
| + | except Exception: | ||
| + | pass | ||
| + | root.destroy() | ||
| + | |||
| + | # Socket connection and main setup | ||
| + | sock = socket.create_connection((ESP32_IP, | ||
| + | sock.settimeout(1.0) | ||
| + | csvfile = open(CSV_FILENAME, | ||
| + | writer = csv.writer(csvfile) | ||
| + | header = [" | ||
| + | writer.writerow(header) | ||
| + | |||
| + | receiver_thread = threading.Thread(target=data_receiver, | ||
| + | receiver_thread.start() | ||
| + | |||
| + | root = tk.Tk() | ||
| + | root.title(" | ||
| + | |||
| + | fig, ax = plt.subplots() | ||
| + | im = ax.imshow(np.zeros((8 * UPSCALE_FACTOR, | ||
| + | canvas = FigureCanvasTkAgg(fig, | ||
| + | canvas.get_tk_widget().pack() | ||
| + | |||
| + | close_button = tk.Button(root, | ||
| + | close_button.pack(pady=10) | ||
| + | |||
| + | root.after(100, | ||
| + | root.protocol(" | ||
| + | root.mainloop() | ||
| + | </ | ||
| + | |||
| + | **Graphic result** | ||
| + | {{ : | ||
| ===== Results ===== | ===== Results ===== | ||
| Line 186: | Line 791: | ||
| * The system is resilient to network disconnects, | * The system is resilient to network disconnects, | ||
| + | ==== Pictures of the prototype ==== | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | |||
| + | ==== Data analysis ==== | ||
| + | |||
| + | **Data Cleaning:** | ||
| + | Selection of a segment of interest, discarding outliers or unreliable data. | ||
| + | Due some hardware limitations, | ||
| + | The expected data is 12 marks readings, but due to problems in the data acquisition there were just 8 marks usable, and there were also some duplicated readings, this was determined manually based on the time and pattern expected. | ||
| + | |||
| + | **Path Segmentation: | ||
| + | getPath(df) constructs segments (" | ||
| + | |||
| + | **Speed Calculation: | ||
| + | mean_speed(data) estimates the average speed between time marks by measuring intervals between zeros. | ||
| + | |||
| + | * After interpolating sensor data along the location axis (temporal/ | ||
| + | |||
| + | * This spatial interpolation significantly enhances intra-frame resolution. | ||
| + | |||
| + | * The aggregation step then merges these larger frames horizontally with value averaging over overlapping columns, preserving continuity. | ||
| + | |||
| + | * The plot displays a much higher-resolution heatmap representing the sensor data over the scanned path. | ||
| + | |||
| + | <code python> | ||
| + | import pandas as pd | ||
| + | import matplotlib.pyplot as plt | ||
| + | import numpy as np | ||
| + | from scipy.ndimage import zoom | ||
| + | from scipy.interpolate import interp1d | ||
| + | |||
| + | fname1 = " | ||
| + | df = pd.read_csv(fname1) | ||
| + | #Since the marks acquisition is not reliable enough, the data has to be treated manually to discard unuseful data | ||
| + | dataFrame = df.loc[1419: | ||
| + | |||
| + | def getPath(df): | ||
| + | idx = list(df.loc[df[" | ||
| + | path_list = [] | ||
| + | for i in range(len(idx)): | ||
| + | if (i+1)%4 == 0 and i != 0: | ||
| + | path_list.append(df.loc[idx[i-3]: | ||
| + | # | ||
| + | return path_list | ||
| + | |||
| + | def mean_speed(data): | ||
| + | idx = list(data.loc[data[" | ||
| + | t1 = data.loc[idx[1]][" | ||
| + | t2 = data.loc[idx[2]][" | ||
| + | t3 = data.loc[idx[3]][" | ||
| + | spd1 = 200/t1 | ||
| + | spd2 = 1000/t2 | ||
| + | spd3 = 1000/t3 | ||
| + | return (spd1+spd2+spd3)/ | ||
| + | |||
| + | paths = getPath(dataFrame) | ||
| + | |||
| + | # 1. Calculate continuous locations and concatenate all paths as before: | ||
| + | for i in range(len(paths)): | ||
| + | loc = (paths[i][" | ||
| + | paths[i] = pd.concat([paths[i], | ||
| + | paths[i] = paths[i].drop(list(paths[i].loc[paths[i][" | ||
| + | |||
| + | path_t = pd.concat(paths) | ||
| + | path_t = path_t.sort_values(" | ||
| + | |||
| + | locations = path_t[' | ||
| + | data_values = path_t.iloc[:, | ||
| + | |||
| + | # 2. Interpolate sensor columns independently on a uniform location grid: | ||
| + | min_loc, max_loc = np.min(locations), | ||
| + | num_interp_points = int(np.ceil(max_loc - min_loc)) + 1 | ||
| + | interp_locations = np.linspace(min_loc, | ||
| + | |||
| + | interp_data = np.zeros((num_interp_points, | ||
| + | for col in range(data_values.shape[1]): | ||
| + | interp_func = interp1d(locations, | ||
| + | interp_data[:, | ||
| + | |||
| + | # 3. Reshape each row into 8x8 frames: | ||
| + | num_frames = interp_data.shape[0] | ||
| + | frame_height, | ||
| + | frames_8x8 = [interp_data[i].reshape(frame_height, | ||
| + | |||
| + | # 4. Interpolate each 8x8 frame to 64x64 using scipy.ndimage.zoom: | ||
| + | zoom_factor = 64 / 8 # 8x to 64x scaling | ||
| + | |||
| + | frames_64x64 = [zoom(frame, | ||
| + | |||
| + | # 5. Aggregate frames horizontally with averaging over overlaps (same as before): | ||
| + | max_offset = num_frames - 1 | ||
| + | final_width = max_offset + 64 # width after scaling frames to 64 wide | ||
| + | final_frame_64 = np.zeros((64, | ||
| + | count_64 = np.zeros((64, | ||
| + | |||
| + | for i, frame in enumerate(frames_64x64): | ||
| + | offset = i | ||
| + | final_frame_64[:, | ||
| + | count_64[:, offset: | ||
| + | |||
| + | aggregated_64 = np.divide(final_frame_64, | ||
| + | |||
| + | # 6. Plot the aggregated 64x wide frame: | ||
| + | plt.figure(figsize=(final_width / 16, 8)) # Adjust size for clarity | ||
| + | plt.imshow(aggregated_64, | ||
| + | plt.colorbar(label=' | ||
| + | plt.title(" | ||
| + | plt.xlabel(' | ||
| + | plt.ylabel(' | ||
| + | plt.show() | ||
| + | </ | ||
| - | ==== Discussion ==== | + | {{ : |
| + | ===== Discussion | ||
| * This system showcases the potential of integrating low-cost sensor networks and automation for sustainable environmental stewardship: | * This system showcases the potential of integrating low-cost sensor networks and automation for sustainable environmental stewardship: | ||
amc/ss2025/group-a/start.1753774617.txt.gz · Last modified: 2025/07/29 09:36 by 35120_students.hsrw