import sensor, image, time, math, socket, network from pyb import Pin SSID = "EkoSlupek" KEY = "1234567890" HOST = "" PORT = 8080 threshold_list = [(190, 255)] min_temp_in_celsius = 20.0 max_temp_in_celsius = 42.0 treshold_temperature = 38.0 # Korekta offsetu temperatury (dodaj/subtrahuj stopnie aby dopasować do referencyjnego termometru) temperature_offset = -2.0 # Przykład: jeśli kamera pokazuje 38°C a Flir 33°C, ustaw -5.0 pin1 = Pin('P4', Pin.OUT_PP, Pin.PULL_NONE) # --- sensor setup --- sensor.reset() sensor.ioctl(sensor.IOCTL_LEPTON_SET_MODE, True, False) sensor.ioctl(sensor.IOCTL_LEPTON_SET_RANGE, min_temp_in_celsius, max_temp_in_celsius) sensor.set_pixformat(sensor.GRAYSCALE) sensor.set_framesize(sensor.QQVGA) sensor.skip_frames(time=2000) # --- WiFi jako Access Point --- try: wlan = network.WLAN(network.AP_IF) wlan.config(ssid=SSID, key=KEY, channel=2) wlan.active(True) print("AP started: {} IP: {}".format(SSID, wlan.ifconfig()[0])) except Exception as e: print("WiFi error:", e) wlan = None def map_g_to_temp(g): temp = ((g * (max_temp_in_celsius - min_temp_in_celsius)) / 255.0) + min_temp_in_celsius return temp + temperature_offset # Dodaj korekcję offsetu def temp_to_g(temp): """Konwertuj temperaturę na wartość grayscale""" return int(((temp - min_temp_in_celsius) / (max_temp_in_celsius - min_temp_in_celsius)) * 255.0) # Próg temperatury - pokazuj tylko bloby powyżej tej temperatury min_display_temp = 35.0 min_display_g = temp_to_g(min_display_temp) # wartość grayscale dla 35°C # Próg dla liczenia średniej - liczymy średnią tylko z pikseli powyżej tej temperatury # To eliminuje problem z chłodnym otoczeniem w prostokącie bloba mean_threshold_temp = 36.5 mean_threshold_g = temp_to_g(mean_threshold_temp) # wartość grayscale dla 36.5°C # Opcja: użyj maksimum zamiast średniej (bardziej wrażliwe na gorączkę, ale może być podatne na zakłócenia) # False = użyj średniej z gorących pikseli (ZALECANE) # True = użyj maksimum (najgorętszy piksel w blobie) use_max_instead_of_mean = False # --- Funkcja restartująca serwer (TYLKO socket, bez restartu AP) --- def restart_server(): global server try: # close old server if exists try: if server: server.close() except: pass # create new server socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) server.bind([HOST, PORT]) server.listen(1) # jedno połączenie naraz server.setblocking(False) print("Server ready on port", PORT) except Exception as e: print("Server restart error:", e) server = None # --- inicjalizacja --- server = None client = None last_client_activity = time.ticks_ms() # aktualizacja przy każdym udanym send/accept restart_cooldown = time.ticks_ms() # do ograniczenia częstotliwości restartów clock = time.clock() # Czas wykrycia gorączki - dla redukcji zakłóceń (opóźnienie 1 sekunda) fever_start_time = None fever_detection_delay_ms = 1000 # Czas opóźnienia w milisekundach (1 sekunda) restart_server() while True: clock.tick() img = sensor.snapshot() # ---- analiza temperatury ---- blob_stats = [] blobs = img.find_blobs(threshold_list, pixels_threshold=200, area_threshold=200, merge=True) for blob in blobs: # Liczymy średnią tylko z pikseli powyżej 36.5°C w obszarze bloba # To eliminuje problem z chłodnym otoczeniem w prostokącie bloba hot_threshold = [(mean_threshold_g, 255)] stats = img.get_statistics(thresholds=hot_threshold, roi=blob.rect()) # Sprawdź czy są jakieś piksele powyżej 36.5°C if stats.mean() >= mean_threshold_g: if use_max_instead_of_mean: # Użyj maksimum (najgorętszy piksel) - bardziej wrażliwe temp_g = stats.max() else: # Użyj średniej z gorących pikseli - bardziej stabilne (ZALECANE) temp_g = stats.mean() temp = map_g_to_temp(temp_g) # Pokazuj tylko bloby z temperaturą > 35°C if temp > min_display_temp: blob_stats.append((blob.x(), blob.y(), temp, blob)) img.to_rainbow(color_palette=image.PALETTE_IRONBOW) # Sprawdź czy którykolwiek blob ma przekroczoną temperaturę has_fever = False # Rysuj tylko bloby które mają mean > 35°C for blob_stat in blob_stats: blob = blob_stat[3] # ostatni element to obiekt blob # Rysuj elipsę zamiast prostokąta - lepiej pasuje do kształtu try: img.draw_ellipse(blob.cx(), blob.cy(), int(blob.w()//2), int(blob.h()//2), 0, color=(0,255,0)) except: # Fallback: prostokąt jeśli elipsa nie działa img.draw_rectangle(blob.rect(), color=(0,255,0)) img.draw_cross(blob.cx(), blob.cy(), color=(0,255,0)) img.draw_string(blob_stat[0]+2, blob_stat[1]+1, "%.2fC" % blob_stat[2], mono_space=False, color=(0,255,0)) if blob_stat[2] > treshold_temperature: has_fever = True # Sterowanie pinem z opóźnieniem 1 sekunda (redukcja zakłóceń) current_time = time.ticks_ms() if has_fever: # Gorączka wykryta - sprawdź czy jest wykrywana wystarczająco długo if fever_start_time is None: # Pierwsze wykrycie - zapisz czas fever_start_time = current_time else: # Sprawdź czy minęło wystarczająco dużo czasu (>1 sekunda) if time.ticks_diff(current_time, fever_start_time) >= fever_detection_delay_ms: # Gorączka wykrywana dłużej niż 1 sekunda - włącz pin pin1.value(1) # Jeśli jeszcze nie minęła sekunda, pin pozostaje w poprzednim stanie else: # Brak gorączki - resetuj timer i wyłącz pin fever_start_time = None pin1.value(0) # ---- obsługa WiFi: jeśli interfejs się zawiesi -> wypisz i spróbuj ponownie (ale nie zamykamy AP bez potrzeby) ---- if wlan and not wlan.active(): print("WiFi down — trying to re-enable AP...") try: wlan.active(False) time.sleep_ms(500) wlan.active(True) wlan.config(ssid=SSID, key=KEY, channel=2) print("AP restarted: {} IP: {}".format(SSID, wlan.ifconfig()[0])) # po restarcie interfejsu trzeba odtworzyć server socket restart_server() except Exception as e: print("WiFi restart error:", e) # przejdź dalej w pętli continue # ---- obsługa połączeń (server socket) ---- if server: if not client: # spróbuj acceptować (non-blocking) try: client, addr = server.accept() print("New client:", addr) client.settimeout(0.1) client.send( "HTTP/1.1 200 OK\r\n" "Server: OpenMV\r\n" "Content-Type: multipart/x-mixed-replace;boundary=openmv\r\n\r\n" ) # zaktualizuj timestamp aktywności przy udanym połączeniu last_client_activity = time.ticks_ms() except OSError: # brak nowego klienta — nic nie rób client = None elif client: # jeśli mamy klienta, wysyłaj ramki try: cframe = img.to_jpeg(quality=70, copy=True) header = "\r\n--openmv\r\nContent-Type: image/jpeg\r\nContent-Length:{}\r\n\r\n".format(cframe.size()) client.send(header) client.send(cframe) # update ostatniej udanej wysyłki last_client_activity = time.ticks_ms() except OSError as e: # klient rozłączył się lub socket padł -> zamykamy klienta i odtwarzamy server socket print("⚠️ Client disconnected or socket error:", e) try: client.close() except: pass client = None # restartujemy TYLKO server socket (workaround WINC bug) if time.ticks_diff(time.ticks_ms(), restart_cooldown) > 2000: restart_cooldown = time.ticks_ms() print("♻️ Recreating server socket (socket-only restart)...") try: if server: try: server.close() except: pass restart_server() except Exception as e2: print("Error recreating server socket:", e2) # krótka pauza, żeby nie próbować od razu accept w tej samej iteracji time.sleep_ms(200) # ---- watchdog: jeśli przez >10 s brak udanej wysyłki/aktywności -> odtwórz server socket (bez restartu AP) ---- if time.ticks_diff(time.ticks_ms(), last_client_activity) > 10000: # ochronna blokada przed zbyt częstymi restartami if time.ticks_diff(time.ticks_ms(), restart_cooldown) > 2000: print("⏰ No client activity for >10s — recreating server socket (watchdog).") try: if server: try: server.close() except: pass restart_server() except Exception as e: print("Watchdog server recreate error:", e) client = None last_client_activity = time.ticks_ms() restart_cooldown = time.ticks_ms() time.sleep_ms(200)