main.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import sensor, image, time, math, socket, network
  2. from pyb import Pin
  3. SSID = "EkoSlupek"
  4. KEY = "1234567890"
  5. HOST = ""
  6. PORT = 8080
  7. threshold_list = [(190, 255)]
  8. min_temp_in_celsius = 20.0
  9. max_temp_in_celsius = 42.0
  10. treshold_temperature = 38.0
  11. # Korekta offsetu temperatury (dodaj/subtrahuj stopnie aby dopasować do referencyjnego termometru)
  12. temperature_offset = -2.0 # Przykład: jeśli kamera pokazuje 38°C a Flir 33°C, ustaw -5.0
  13. pin1 = Pin('P4', Pin.OUT_PP, Pin.PULL_NONE)
  14. # --- sensor setup ---
  15. sensor.reset()
  16. sensor.ioctl(sensor.IOCTL_LEPTON_SET_MODE, True, False)
  17. sensor.ioctl(sensor.IOCTL_LEPTON_SET_RANGE, min_temp_in_celsius, max_temp_in_celsius)
  18. sensor.set_pixformat(sensor.GRAYSCALE)
  19. sensor.set_framesize(sensor.QQVGA)
  20. sensor.skip_frames(time=2000)
  21. # --- WiFi jako Access Point ---
  22. try:
  23. wlan = network.WLAN(network.AP_IF)
  24. wlan.config(ssid=SSID, key=KEY, channel=2)
  25. wlan.active(True)
  26. print("AP started: {} IP: {}".format(SSID, wlan.ifconfig()[0]))
  27. except Exception as e:
  28. print("WiFi error:", e)
  29. wlan = None
  30. def map_g_to_temp(g):
  31. temp = ((g * (max_temp_in_celsius - min_temp_in_celsius)) / 255.0) + min_temp_in_celsius
  32. return temp + temperature_offset # Dodaj korekcję offsetu
  33. def temp_to_g(temp):
  34. """Konwertuj temperaturę na wartość grayscale"""
  35. return int(((temp - min_temp_in_celsius) / (max_temp_in_celsius - min_temp_in_celsius)) * 255.0)
  36. # Próg temperatury - pokazuj tylko bloby powyżej tej temperatury
  37. min_display_temp = 35.0
  38. min_display_g = temp_to_g(min_display_temp) # wartość grayscale dla 35°C
  39. # Próg dla liczenia średniej - liczymy średnią tylko z pikseli powyżej tej temperatury
  40. # To eliminuje problem z chłodnym otoczeniem w prostokącie bloba
  41. mean_threshold_temp = 36.5
  42. mean_threshold_g = temp_to_g(mean_threshold_temp) # wartość grayscale dla 36.5°C
  43. # Opcja: użyj maksimum zamiast średniej (bardziej wrażliwe na gorączkę, ale może być podatne na zakłócenia)
  44. # False = użyj średniej z gorących pikseli (ZALECANE)
  45. # True = użyj maksimum (najgorętszy piksel w blobie)
  46. use_max_instead_of_mean = False
  47. # --- Funkcja restartująca serwer (TYLKO socket, bez restartu AP) ---
  48. def restart_server():
  49. global server
  50. try:
  51. # close old server if exists
  52. try:
  53. if server:
  54. server.close()
  55. except:
  56. pass
  57. # create new server socket
  58. server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  59. server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
  60. server.bind([HOST, PORT])
  61. server.listen(1) # jedno połączenie naraz
  62. server.setblocking(False)
  63. print("Server ready on port", PORT)
  64. except Exception as e:
  65. print("Server restart error:", e)
  66. server = None
  67. # --- inicjalizacja ---
  68. server = None
  69. client = None
  70. last_client_activity = time.ticks_ms() # aktualizacja przy każdym udanym send/accept
  71. restart_cooldown = time.ticks_ms() # do ograniczenia częstotliwości restartów
  72. clock = time.clock()
  73. # Czas wykrycia gorączki - dla redukcji zakłóceń (opóźnienie 1 sekunda)
  74. fever_start_time = None
  75. fever_detection_delay_ms = 1000 # Czas opóźnienia w milisekundach (1 sekunda)
  76. restart_server()
  77. while True:
  78. clock.tick()
  79. img = sensor.snapshot()
  80. # ---- analiza temperatury ----
  81. blob_stats = []
  82. blobs = img.find_blobs(threshold_list, pixels_threshold=200, area_threshold=200, merge=True)
  83. for blob in blobs:
  84. # Liczymy średnią tylko z pikseli powyżej 36.5°C w obszarze bloba
  85. # To eliminuje problem z chłodnym otoczeniem w prostokącie bloba
  86. hot_threshold = [(mean_threshold_g, 255)]
  87. stats = img.get_statistics(thresholds=hot_threshold, roi=blob.rect())
  88. # Sprawdź czy są jakieś piksele powyżej 36.5°C
  89. if stats.mean() >= mean_threshold_g:
  90. if use_max_instead_of_mean:
  91. # Użyj maksimum (najgorętszy piksel) - bardziej wrażliwe
  92. temp_g = stats.max()
  93. else:
  94. # Użyj średniej z gorących pikseli - bardziej stabilne (ZALECANE)
  95. temp_g = stats.mean()
  96. temp = map_g_to_temp(temp_g)
  97. # Pokazuj tylko bloby z temperaturą > 35°C
  98. if temp > min_display_temp:
  99. blob_stats.append((blob.x(), blob.y(), temp, blob))
  100. img.to_rainbow(color_palette=image.PALETTE_IRONBOW)
  101. # Sprawdź czy którykolwiek blob ma przekroczoną temperaturę
  102. has_fever = False
  103. # Rysuj tylko bloby które mają mean > 35°C
  104. for blob_stat in blob_stats:
  105. blob = blob_stat[3] # ostatni element to obiekt blob
  106. # Rysuj elipsę zamiast prostokąta - lepiej pasuje do kształtu
  107. try:
  108. img.draw_ellipse(blob.cx(), blob.cy(), int(blob.w()//2), int(blob.h()//2), 0, color=(0,255,0))
  109. except:
  110. # Fallback: prostokąt jeśli elipsa nie działa
  111. img.draw_rectangle(blob.rect(), color=(0,255,0))
  112. img.draw_cross(blob.cx(), blob.cy(), color=(0,255,0))
  113. img.draw_string(blob_stat[0]+2, blob_stat[1]+1, "%.2fC" % blob_stat[2], mono_space=False, color=(0,255,0))
  114. if blob_stat[2] > treshold_temperature:
  115. has_fever = True
  116. # Sterowanie pinem z opóźnieniem 1 sekunda (redukcja zakłóceń)
  117. current_time = time.ticks_ms()
  118. if has_fever:
  119. # Gorączka wykryta - sprawdź czy jest wykrywana wystarczająco długo
  120. if fever_start_time is None:
  121. # Pierwsze wykrycie - zapisz czas
  122. fever_start_time = current_time
  123. else:
  124. # Sprawdź czy minęło wystarczająco dużo czasu (>1 sekunda)
  125. if time.ticks_diff(current_time, fever_start_time) >= fever_detection_delay_ms:
  126. # Gorączka wykrywana dłużej niż 1 sekunda - włącz pin
  127. pin1.value(1)
  128. # Jeśli jeszcze nie minęła sekunda, pin pozostaje w poprzednim stanie
  129. else:
  130. # Brak gorączki - resetuj timer i wyłącz pin
  131. fever_start_time = None
  132. pin1.value(0)
  133. # ---- obsługa WiFi: jeśli interfejs się zawiesi -> wypisz i spróbuj ponownie (ale nie zamykamy AP bez potrzeby) ----
  134. if wlan and not wlan.active():
  135. print("WiFi down — trying to re-enable AP...")
  136. try:
  137. wlan.active(False)
  138. time.sleep_ms(500)
  139. wlan.active(True)
  140. wlan.config(ssid=SSID, key=KEY, channel=2)
  141. print("AP restarted: {} IP: {}".format(SSID, wlan.ifconfig()[0]))
  142. # po restarcie interfejsu trzeba odtworzyć server socket
  143. restart_server()
  144. except Exception as e:
  145. print("WiFi restart error:", e)
  146. # przejdź dalej w pętli
  147. continue
  148. # ---- obsługa połączeń (server socket) ----
  149. if server:
  150. if not client:
  151. # spróbuj acceptować (non-blocking)
  152. try:
  153. client, addr = server.accept()
  154. print("New client:", addr)
  155. client.settimeout(0.1)
  156. client.send(
  157. "HTTP/1.1 200 OK\r\n"
  158. "Server: OpenMV\r\n"
  159. "Content-Type: multipart/x-mixed-replace;boundary=openmv\r\n\r\n"
  160. )
  161. # zaktualizuj timestamp aktywności przy udanym połączeniu
  162. last_client_activity = time.ticks_ms()
  163. except OSError:
  164. # brak nowego klienta — nic nie rób
  165. client = None
  166. elif client:
  167. # jeśli mamy klienta, wysyłaj ramki
  168. try:
  169. cframe = img.to_jpeg(quality=70, copy=True)
  170. header = "\r\n--openmv\r\nContent-Type: image/jpeg\r\nContent-Length:{}\r\n\r\n".format(cframe.size())
  171. client.send(header)
  172. client.send(cframe)
  173. # update ostatniej udanej wysyłki
  174. last_client_activity = time.ticks_ms()
  175. except OSError as e:
  176. # klient rozłączył się lub socket padł -> zamykamy klienta i odtwarzamy server socket
  177. print("⚠️ Client disconnected or socket error:", e)
  178. try:
  179. client.close()
  180. except:
  181. pass
  182. client = None
  183. # restartujemy TYLKO server socket (workaround WINC bug)
  184. if time.ticks_diff(time.ticks_ms(), restart_cooldown) > 2000:
  185. restart_cooldown = time.ticks_ms()
  186. print("♻️ Recreating server socket (socket-only restart)...")
  187. try:
  188. if server:
  189. try:
  190. server.close()
  191. except:
  192. pass
  193. restart_server()
  194. except Exception as e2:
  195. print("Error recreating server socket:", e2)
  196. # krótka pauza, żeby nie próbować od razu accept w tej samej iteracji
  197. time.sleep_ms(200)
  198. # ---- watchdog: jeśli przez >10 s brak udanej wysyłki/aktywności -> odtwórz server socket (bez restartu AP) ----
  199. if time.ticks_diff(time.ticks_ms(), last_client_activity) > 10000:
  200. # ochronna blokada przed zbyt częstymi restartami
  201. if time.ticks_diff(time.ticks_ms(), restart_cooldown) > 2000:
  202. print("⏰ No client activity for >10s — recreating server socket (watchdog).")
  203. try:
  204. if server:
  205. try:
  206. server.close()
  207. except:
  208. pass
  209. restart_server()
  210. except Exception as e:
  211. print("Watchdog server recreate error:", e)
  212. client = None
  213. last_client_activity = time.ticks_ms()
  214. restart_cooldown = time.ticks_ms()
  215. time.sleep_ms(200)