AI-based Aquatic Ultrasonic Imaging & Chemical Water Testing
Identify noxious air bubbles lurking in the substrate w/ ultrasonic scans and assess water pollution based on chemical tests simultaneously.
Components and supplies
DFRobot Serial 6-Axis Accelerometer
Anycubic Kobra 2 Max
4.7K ohm resistor
DFRobot URM15 - 75KHZ Ultrasonic Sensor
SSD1306 OLED Display
DS18B20 temperature sensor
LattePanda 3 Delta 864
UNIHIKER
Arduino® Nano ESP32
ELECROW Custom PCB
KITRONIK Edge Connector for BBC micro:bit
DFRobot Gravity: RS485-to-UART Signal Adapter Module
USB Webcam (PK-910H)
Tools and machines
Soldering Station
Hot glue gun (generic)
Apps and platforms
Arduino IDE
NVIDIA TAO RetinaNet
Edge Impulse Studio
VS Code
Telegram
Fusion 360
Ultimate Cura
MobaXterm
KiCad
XAMPP Server
Thonny IDE
OpenCV
Project description
Code
AIoT_Aquatic_Ultrasonic_Imaging.ino
cpp
AIoT_Aquatic_Ultrasonic_Imaging.ino
1///////////////////////////////////////////// 2 // AI-based Aquatic Ultrasonic Imaging // 3 // & Chemical Water Testing // 4 // --------------- // 5 // (Arduino Nano ESP32) // 6 // by Kutluhan Aktar // 7 // // 8 ///////////////////////////////////////////// 9 10// 11// Identify noxious air bubbles lurking in the substrate w/ ultrasonic scans and assess water pollution based on chemical tests simultaneously. 12// 13// For more information: 14// https://www.hackster.io/kutluhan-aktar 15// 16// 17// Connections 18// Arduino Nano ESP32 : 19// URM15 - 75KHZ Ultrasonic Sensor via RS485-to-UART Signal Adapter Module 20// D3 ------------------------ TX 21// D2 ------------------------ RX 22// 3.3V ------------------------ + 23// GND ------------------------ - 24// Serial 6-Axis Accelerometer 25// 3.3V ------------------------ VCC 26// D5 ------------------------ RXD 27// D4 ------------------------ TXD 28// GND ------------------------ GND 29// DS18B20 Waterproof Temperature Sensor 30// A1 ------------------------ Data 31// SSD1306 OLED Display (128x64) 32// A4 ------------------------ SDA 33// A5 ------------------------ SCL 34// Control Button (A) 35// D6 ------------------------ + 36// Control Button (B) 37// D7 ------------------------ + 38// Control Button (C) 39// D8 ------------------------ + 40// Control Button (D) 41// D9 ------------------------ + 42// 5mm Common Anode RGB LED 43// D10 ------------------------ R 44// D11 ------------------------ G 45// D12 ------------------------ B 46 47// Include the required libraries: 48#include <WiFi.h> 49#include "DFRobot_RTU.h" 50#include <DFRobot_WT61PC.h> 51#include <OneWire.h> 52#include <DallasTemperature.h> 53#include <Adafruit_GFX.h> 54#include <Adafruit_SSD1306.h> 55 56// Add the icons to be shown on the SSD1306 OLED display. 57#include "logo.h" 58 59// Include the Edge Impulse neural network model converted to an Arduino library: 60#include <Aquatic_Air_Bubble_Detection_inferencing.h> 61 62// Define the required parameters to run an inference with the Edge Impulse neural network model. 63#define sample_buffer_size 400 64 65// Define the threshold value for the model outputs (predictions). 66float threshold = 0.60; 67 68// Define the air bubble class names: 69String classes[] = {"bubble", "normal"}; 70 71char ssid[] = "<________>"; // your network SSID (name) 72char pass[] = "<________>"; // your network password (use for WPA, or use as key for WEP) 73int keyIndex = 0; // your network key Index number (needed only for WEP) 74 75// Define the server on LattePanda 3 Delta 864. 76char server[] = "192.168.1.22"; 77// Define the web application path. 78String application = "/Aquatic_Ultrasonic_Imaging/"; 79 80// Initialize the WiFiClient object. 81WiFiClient client; /* WiFiSSLClient client; */ 82 83// Define the buffer (array) to save the ultrasonic scan variables — 20 x 20 image (400 data points). 84#define scan_buffer_size 400 85float ultrasonic_scan[scan_buffer_size] = {0}; 86 87// Define the URM15 ultrasonic sensor address to register variables. 88#define SLAVE_ADDR ((uint16_t)0x0F) 89 90typedef enum{ 91 ePid, 92 eVid, 93 eAddr, 94 eComBaudrate, 95 eComParityStop, 96 eDistance, 97 eInternalTempreture, 98 eExternTempreture, 99 eControl 100}eRegIndex_t; 101 102// Define the modbus object to utilize the RS485-to-UART signal transfer module with the ultrasonic sensor. 103DFRobot_RTU modbus(/*s =*/&Serial1); 104 105// Define the 6-axis accelerometer object. 106DFRobot_WT61PC accelerometer(&Serial2); 107 108// Define the DS18B20 waterproof temperature sensor settings: 109#define ONE_WIRE_BUS A1 110OneWire oneWire(ONE_WIRE_BUS); 111DallasTemperature DS18B20(&oneWire); 112 113// Define the SSD1306 screen settings: 114#define SCREEN_WIDTH 128 // OLED display width, in pixels 115#define SCREEN_HEIGHT 64 // OLED display height, in pixels 116#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) 117 118Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); 119 120// Create a struct (_data) including all measurements generated by the 6-axis accelerometer: 121struct _data { 122 float acc_x; 123 float acc_y; 124 float acc_z; 125 float gyro_x; 126 float gyro_y; 127 float gyro_z; 128 float ang_x; 129 float ang_y; 130 float ang_z; 131}; 132 133// Define the RGB pin settings. 134#define red_pin D10 135#define green_pin D11 136#define blue_pin D12 137 138// Define the control buttons. 139#define control_button_A D6 140#define control_button_B D7 141#define control_button_C D8 142#define control_button_D D9 143 144// Define the data holders: 145#define RX_1_PIN D2 146#define TX_1_PIN D3 147#define RX_2_PIN D4 148#define TX_2_PIN D5 149int predicted_class = -1; 150int menu_option = 0, scanned_points = -1; 151volatile boolean selected_interface[] = {false, false, false, false}; 152float water_temperature, distance; 153struct _data _acc; 154 155void setup(){ 156 Serial.begin(115200); 157 158 pinMode(control_button_A, INPUT_PULLUP); pinMode(control_button_B, INPUT_PULLUP); pinMode(control_button_C, INPUT_PULLUP); pinMode(control_button_D, INPUT_PULLUP); 159 pinMode(red_pin, OUTPUT); pinMode(green_pin, OUTPUT); pinMode(blue_pin, OUTPUT); 160 adjustColor(0,0,0); 161 162 // Define the first hardware serial port to communicate with the URM15 ultrasonic sensor via the RS485-to-UART signal adapter module. 163 Serial1.begin(19200, SERIAL_8N1, RX_1_PIN, TX_1_PIN); 164 165 // Define the second hardware serial port to communicate with the 6-axis accelerometer. 166 Serial2.begin(9600, SERIAL_8N1, RX_2_PIN, TX_2_PIN); 167 168 // Initialize the SSD1306 screen: 169 display.begin(SSD1306_SWITCHCAPVCC, 0x3C); 170 display.display(); 171 delay(1000); 172 173 // Set the URM15 ultrasonic sensor to trigger mode, select the external temperature compensation, and enable the temperature compensation function by writing the control register variable — byte (LSB). 174 /* 175 bit0: 176 0 - select onboard temperature 177 1 - select external temperature 178 bit1: 179 0 - enable temperature compensation function 180 1 - disable temperature compensation function 181 bit2: 182 0 - activate auto detection 183 1 - activate passive detection 184 bit3: 185 1 - read distance every 65 ms (in passive detection mode) 186 */ 187 modbus.writeHoldingRegister(/*id =*/SLAVE_ADDR, /*reg =*/ eControl, /*val =*/0b00000001); 188 delay(1000); 189 190 // Configure the data output frequency of the 6-axis accelerometer. 191 accelerometer.modifyFrequency(FREQUENCY_200HZ); /* FREQUENCY_0_1HZ, FREQUENCY_0_5HZ, FREQUENCY_1HZ, FREQUENCY_2HZ, FREQUENCY_5HZ, FREQUENCY_10HZ, FREQUENCY_20HZ, FREQUENCY_50HZ, FREQUENCY_100HZ, FREQUENCY_125HZ, FREQUENCY_200HZ */ 192 193 // Initialize the DS18B20 temperature sensor. 194 DS18B20.begin(); 195 196 // Connect to WPA/WPA2 network. Change this line if using an open or WEP network. 197 WiFi.mode(WIFI_STA); 198 WiFi.begin(ssid, pass); 199 // Attempt to connect to the given Wi-Fi network. 200 while(WiFi.status() != WL_CONNECTED){ 201 // Wait for the network connection. 202 delay(500); 203 Serial.print("."); 204 } 205 // If connected to the network successfully: 206 Serial.println("Connected to the Wi-Fi network successfully!"); 207} 208 209void loop(){ 210 // Adjust the highlighted menu option by utilizing the control buttons — A and C. 211 if(!digitalRead(control_button_A)){ 212 menu_option-=1; 213 if(menu_option < 0) menu_option = 4; 214 delay(500); 215 } 216 if(!digitalRead(control_button_C)){ 217 menu_option+=1; 218 if(menu_option > 4) menu_option = 0; 219 delay(500); 220 } 221 222 // Show the interface (home) screen. 223 show_interface("home", menu_option); 224 225 // If the control button B is pressed, navigate to the selected interface (menu) option. 226 if(!digitalRead(control_button_B) && menu_option == 1){ 227 selected_interface[menu_option-1] = true; 228 adjustColor(255,255,0); 229 while(selected_interface[menu_option-1]){ 230 // Read multiple sensor data packets. 231 read_ultrasonic_sensor(get_temperature()); 232 read_accelerometer(); 233 // Display the retrieved sensor information on the SSD1306 screen. 234 show_interface("sensor", menu_option); 235 // If the control button D is pressed, redirect the user to the home screen. 236 if(!digitalRead(control_button_D)){ 237 selected_interface[menu_option-1] = false; 238 adjustColor(0,0,0); 239 } 240 } 241 } 242 243 if(!digitalRead(control_button_B) && menu_option == 2){ 244 selected_interface[menu_option-1] = true; 245 adjustColor(0,255,255); 246 // Clear the data buffer. 247 scanned_points = -1; 248 while(selected_interface[menu_option-1]){ 249 // Read multiple sensor data packets. 250 read_ultrasonic_sensor(get_temperature()); 251 read_accelerometer(); 252 // Initiate the ultrasonic image scanning procedure. 253 ultrasonic_imaging(); 254 // Display the ultrasonic scanning progress on the SSD1306 screen. 255 show_interface("scan", menu_option); 256 // If the control button D is pressed, redirect the user to the home screen. 257 if(!digitalRead(control_button_D)){ 258 selected_interface[menu_option-1] = false; 259 adjustColor(0,0,0); 260 } 261 } 262 } 263 264 if(!digitalRead(control_button_B) && menu_option == 3){ 265 selected_interface[menu_option-1] = true; 266 adjustColor(255,0,255); 267 while(selected_interface[menu_option-1]){ 268 // Display the selectable labels (air bubble classes). 269 show_interface("save", menu_option); 270 // Depending on the passed air bubble class via the control buttons (A and C), transfer the collected ultrasonic scan data (buffer) to the web application via an HTTP POST request. 271 if(!digitalRead(control_button_A)){ 272 if(make_a_post_request("?scan=OK&type=sample&class=normal")){ 273 // If successful: 274 display.clearDisplay(); 275 display.drawBitmap((SCREEN_WIDTH-connected_width)/2, (SCREEN_HEIGHT-connected_height)/2, connected_bits, connected_width, connected_height, SSD1306_WHITE); 276 display.display(); 277 adjustColor(0,255,0); 278 delay(2000); 279 adjustColor(255,0,255); 280 }else{ 281 display.clearDisplay(); 282 display.drawBitmap((SCREEN_WIDTH-error_width)/2, (SCREEN_HEIGHT-error_height)/2, error_bits, error_width, error_height, SSD1306_WHITE); 283 display.display(); 284 adjustColor(255,0,0); 285 delay(2000); 286 adjustColor(255,0,255); 287 } 288 } 289 if(!digitalRead(control_button_C)){ 290 if(make_a_post_request("?scan=OK&type=sample&class=bubble")){ 291 // If successful: 292 display.clearDisplay(); 293 display.drawBitmap((SCREEN_WIDTH-connected_width)/2, (SCREEN_HEIGHT-connected_height)/2, connected_bits, connected_width, connected_height, SSD1306_WHITE); 294 display.display(); 295 adjustColor(0,255,0); 296 delay(2000); 297 adjustColor(255,0,255); 298 }else{ 299 display.clearDisplay(); 300 display.drawBitmap((SCREEN_WIDTH-error_width)/2, (SCREEN_HEIGHT-error_height)/2, error_bits, error_width, error_height, SSD1306_WHITE); 301 display.display(); 302 adjustColor(255,0,0); 303 delay(2000); 304 adjustColor(255,0,255); 305 } 306 } 307 // If the control button D is pressed, redirect the user to the home screen. 308 if(!digitalRead(control_button_D)){ 309 selected_interface[menu_option-1] = false; 310 adjustColor(0,0,0); 311 } 312 } 313 } 314 315 if(!digitalRead(control_button_B) && menu_option == 4){ 316 selected_interface[menu_option-1] = true; 317 adjustColor(255,255,255); 318 while(selected_interface[menu_option-1]){ 319 // Display the running inference progress on the SSD1306 screen. 320 show_interface("run", menu_option); 321 // If the control button A is pressed, run the Edge Impulse neural network model to detect aquatic air bubbles by applying the ultrasonic scan data points collected via the URM15 ultrasonic sensor. 322 if(!digitalRead(control_button_A)){ 323 // Run inference. 324 run_inference_to_make_predictions(true); 325 delay(2000); 326 } 327 // After running the neural network model successfully, if the control button C is pressed, transfer the applied data record (ultrasonic scan buffer) and the detected air bubble class to the web application via an HTTP POST request. 328 if(!digitalRead(control_button_C) && predicted_class > -1){ 329 if(make_a_post_request("?scan=OK&type=detection&class=" + classes[predicted_class])){ 330 // If successful: 331 display.clearDisplay(); 332 display.drawBitmap((SCREEN_WIDTH-connected_width)/2, (SCREEN_HEIGHT-connected_height)/2, connected_bits, connected_width, connected_height, SSD1306_WHITE); 333 display.display(); 334 adjustColor(0,255,0); 335 delay(2000); 336 adjustColor(255,255,255); 337 }else{ 338 display.clearDisplay(); 339 display.drawBitmap((SCREEN_WIDTH-error_width)/2, (SCREEN_HEIGHT-error_height)/2, error_bits, error_width, error_height, SSD1306_WHITE); 340 display.display(); 341 adjustColor(255,0,0); 342 delay(2000); 343 adjustColor(255,255,255); 344 } 345 } 346 // If the control button D is pressed, redirect the user to the home screen. 347 if(!digitalRead(control_button_D)){ 348 selected_interface[menu_option-1] = false; 349 adjustColor(0,0,0); 350 // Clear the predicted class (label). 351 predicted_class = -1; 352 } 353 } 354 } 355 356} 357 358void run_inference_to_make_predictions(bool _r){ 359 // Summarize the Edge Impulse neural network model inference settings (from model_metadata.h): 360 Serial.print("\nInference settings:\n"); 361 Serial.print("\tInterval: "); Serial.print((float)EI_CLASSIFIER_INTERVAL_MS); Serial.print(" ms.\n"); 362 Serial.printf("\tFrame size: %d\n", EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE); 363 Serial.printf("\tSample length: %d ms.\n", EI_CLASSIFIER_RAW_SAMPLE_COUNT / 16); 364 Serial.printf("\tNo. of classes: %d\n", sizeof(ei_classifier_inferencing_categories) / sizeof(ei_classifier_inferencing_categories[0])); 365 366 // If the URM15 ultrasonic sensor generates an ultrasonic scan buffer (20 x 20 — 400 points) successfully: 367 if(ultrasonic_scan[scan_buffer_size-1] > 0){ 368 // Run inference: 369 ei::signal_t signal; 370 // Create a signal object from the resized (scaled) raw data buffer — ultrasonic scan buffer. 371 numpy::signal_from_buffer(ultrasonic_scan, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, &signal); 372 // Run the classifier: 373 ei_impulse_result_t result = { 0 }; 374 EI_IMPULSE_ERROR _err = run_classifier(&signal, &result, false); 375 if(_err != EI_IMPULSE_OK){ 376 Serial.printf("ERR: Failed to run classifier (%d)\n", _err); 377 return; 378 } 379 380 // Print the inference timings on the serial monitor. 381 Serial.printf("\nPredictions (DSP: %d ms., Classification: %d ms., Anomaly: %d ms.): \n", 382 result.timing.dsp, result.timing.classification, result.timing.anomaly); 383 384 // Obtain the prediction results for each label (class). 385 for(size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++){ 386 // Print the prediction results on the serial monitor. 387 Serial.printf("\t%s:\t%.5f\n", result.classification[ix].label, result.classification[ix].value); 388 // Get the imperative predicted label (class). 389 if(result.classification[ix].value >= threshold) predicted_class = ix; 390 } 391 Serial.printf("\nPredicted Class: %d [%s]\n", predicted_class, classes[predicted_class]); 392 393 // Detect anomalies, if any: 394 #if EI_CLASSIFIER_HAS_ANOMALY == 1 395 Serial.printf("Anomaly: %d \n", result.anomaly); 396 #endif 397 398 // Release the ultrasonic scan buffer if requested. 399 if(!_r){ for(int i=0; i<scan_buffer_size; i++){ ultrasonic_scan[i] = 0; } } 400 401 }else{ 402 Serial.println("\nUltrasonic scan data buffer => Empty!"); 403 } 404} 405 406boolean make_a_post_request(String request){ 407 // Connect to the web application named Aquatic_Ultrasonic_Imaging. Change '80' with '443' if you are using SSL connection. 408 if (client.connect(server, 80)){ 409 // If successful: 410 Serial.println("\nConnected to the web application successfully!\n"); 411 // Create the query string: 412 String query = application + request; 413 // Make an HTTP POST request: 414 String head = "--UltrasonicScan\r\nContent-Disposition: form-data; name=\"ultrasonic_scan\"; filename=\"new_scan.txt\"\r\nContent-Type: text/plain\r\n\r\n"; 415 String tail = "\r\n--UltrasonicScan--\r\n"; 416 // Get the total message length. 417 uint32_t totalLen = head.length() + sizeof(ultrasonic_scan) + (scan_buffer_size*sizeof(char)) + tail.length(); 418 // Start the request: 419 client.println("POST " + query + " HTTP/1.1"); 420 client.println("Host: 192.168.1.22"); 421 client.println("Content-Length: " + String(totalLen)); 422 client.println("Connection: Keep-Alive"); 423 client.println("Content-Type: multipart/form-data; boundary=UltrasonicScan"); 424 client.println(); 425 client.print(head); 426 for(int i=0; i<scan_buffer_size; i++){ client.print(ultrasonic_scan[i]); client.print(",");} 427 client.print(tail); 428 // Wait until transferring the ultrasonic scan (text) buffer (20x20). 429 delay(2000); 430 // If successful: 431 Serial.println("HTTP POST => Data transfer completed!\n"); 432 return true; 433 }else{ 434 Serial.println("\nConnection failed to the web application!\n"); 435 delay(2000); 436 return false; 437 } 438} 439 440void ultrasonic_imaging(){ 441 // Define underwater device movements by utilizing the axis measurements generated by the 6-axis accelerometer — acceleration and angular velocity. 442 if(_acc.acc_x > 0 && _acc.gyro_x > 0 && _acc.acc_y > 0 && _acc.gyro_y > 0){ 443 // If the device is moving underwater inside an arbitrary square, collect the temperature-compensated distance measurements produced by the URM15 ultrasonic sensor 444 // and save them as data points to the scan data buffer — 20 x 20 (400 points). 445 if(scanned_points < 399){ 446 scanned_points+=1; 447 ultrasonic_scan[scanned_points] = distance/100; 448 delay(50); 449 }else{ 450 adjustColor(0,255,0); 451 Serial.println("Scan Completed!"); 452 delay(50); 453 } 454 } 455} 456 457void read_ultrasonic_sensor(float water_temp){ 458 // Configure the external temperature value by utilizing the evaluated water temperature to generate precise distance measurements. 459 water_temp = water_temp*10; 460 modbus.writeHoldingRegister(/*id =*/SLAVE_ADDR, /*reg =*/eExternTempreture, /*val =*/water_temp); 461 delay(50); 462 // Obtain the temperature-compensated distance measurement produced by the URM15 ultrasonic sensor. 463 distance = modbus.readHoldingRegister(SLAVE_ADDR, eDistance); 464 delay(50); 465 // If the sensor is out of range, set the distance to -1. 466 if(distance == 65535){ 467 distance = -1; 468 Serial.println("Ultrasonic sensor is out of range!"); 469 }else{ 470 distance = distance/10; 471 } 472 delay(50); 473} 474 475void read_accelerometer(){ 476 // Obtain the X, Y, and Z-axis measurements generated by the 6-axis accelerometer — acceleration, angular velocity, angle. 477 if(accelerometer.available()){ 478 _acc.acc_x = accelerometer.Acc.X; _acc.acc_y = accelerometer.Acc.Y; _acc.acc_z = accelerometer.Acc.Z; 479 _acc.gyro_x = accelerometer.Gyro.X; _acc.gyro_y = accelerometer.Gyro.Y; _acc.gyro_z = accelerometer.Gyro.Z; 480 _acc.ang_x = accelerometer.Angle.X; _acc.ang_y = accelerometer.Angle.Y; _acc.ang_z = accelerometer.Angle.Z; 481 } 482} 483 484float get_temperature(){ 485 // Obtain the temperature measurement in Celsius, estimated by the DS18B20 temperature sensor. 486 DS18B20.requestTemperatures(); 487 float t = DS18B20.getTempCByIndex(0); 488 delay(50); 489 return t; 490} 491 492void show_interface(String com, int menu_option){ 493 // Get the assigned interface logo information. 494 int l_w = interface_widths[menu_option]; 495 int l_h = interface_heights[menu_option]; 496 if(com == "home"){ 497 display.clearDisplay(); 498 display.drawBitmap(0, (SCREEN_HEIGHT-l_h)/2, interface_logos[menu_option], l_w, l_h, SSD1306_WHITE); 499 display.setTextSize(1); 500 (menu_option == 1) ? display.setTextColor(SSD1306_BLACK, SSD1306_WHITE) : display.setTextColor(SSD1306_WHITE); 501 display.setCursor(l_w+5, 5); 502 display.println("1.Show Readings"); 503 (menu_option == 2) ? display.setTextColor(SSD1306_BLACK, SSD1306_WHITE) : display.setTextColor(SSD1306_WHITE); 504 display.setCursor(l_w+5, 20); 505 display.println("2.Ultrasonic+++"); 506 (menu_option == 3) ? display.setTextColor(SSD1306_BLACK, SSD1306_WHITE) : display.setTextColor(SSD1306_WHITE); 507 display.setCursor(l_w+5, 35); 508 display.println("3.Save Samples"); 509 (menu_option == 4) ? display.setTextColor(SSD1306_BLACK, SSD1306_WHITE) : display.setTextColor(SSD1306_WHITE); 510 display.setCursor(l_w+5, 50); 511 display.println("4.Run Inference"); 512 display.display(); 513 delay(500); 514 } 515 else if(com == "sensor"){ 516 display.clearDisplay(); 517 display.drawBitmap(SCREEN_WIDTH-l_w, SCREEN_HEIGHT-l_h, interface_logos[menu_option], l_w, l_h, SSD1306_WHITE); 518 display.setTextSize(1); 519 display.setCursor(0, 0); 520 display.print("Distance: "); display.print(distance); display.println("cm"); 521 display.setCursor(0, 20); 522 display.print("X: "); display.print(_acc.acc_x); display.print(" / "); display.print(_acc.gyro_x); 523 display.setCursor(0, 30); 524 display.print("Y: "); display.print(_acc.acc_y); display.print(" / "); display.print(_acc.gyro_y); 525 display.setCursor(0, 40); 526 display.print("Z: "); display.print(_acc.acc_z); display.print(" / "); display.print(_acc.gyro_z); 527 display.display(); 528 } 529 else if(com == "scan"){ 530 display.clearDisplay(); 531 display.drawBitmap(SCREEN_WIDTH-l_w, SCREEN_HEIGHT-l_h, interface_logos[menu_option], l_w, l_h, SSD1306_WHITE); 532 display.setTextSize(2); 533 display.setCursor(0, 0); 534 display.print(scanned_points+1); display.println(" / 400"); 535 display.setTextSize(1); 536 display.setCursor(0, 25); 537 (scanned_points < 399) ? display.print("Scanning...") : display.print("Scan Completed!"); 538 display.display(); 539 } 540 else if(com == "save"){ 541 display.clearDisplay(); 542 display.drawBitmap((SCREEN_WIDTH-l_w)/2, 0, interface_logos[menu_option], l_w, l_h, SSD1306_WHITE); 543 display.setTextSize(1); 544 display.setCursor(0, l_h+10); 545 display.print("A) Class => normal"); 546 display.setCursor(0, l_h+25); 547 display.print("C) Class => bubble"); 548 display.display(); 549 } 550 else if(com == "run"){ 551 display.clearDisplay(); 552 display.setTextSize(1); 553 display.setTextColor(SSD1306_WHITE); 554 display.setCursor(0, l_h+5); 555 display.print("A) Run Inference"); 556 display.setCursor(0, l_h+20); 557 // Show the latest model detection result and the assigned class icon if the model yields a label successfully. 558 String r = (predicted_class > -1) ? classes[predicted_class] : "Pending"; 559 display.print("C) Send: "+ r); 560 (predicted_class > -1) ? display.drawBitmap((SCREEN_WIDTH-class_widths[predicted_class])/2, 0, class_logos[predicted_class], class_widths[predicted_class], class_heights[predicted_class], SSD1306_WHITE) : display.drawBitmap((SCREEN_WIDTH-l_w)/2, 0, interface_logos[menu_option], l_w, l_h, SSD1306_WHITE); 561 display.display(); 562 } 563} 564 565void adjustColor(int r, int g, int b){ 566 analogWrite(red_pin, (255-r)); 567 analogWrite(green_pin, (255-g)); 568 analogWrite(blue_pin, (255-b)); 569}
Downloadable files
Code Files
Project Code Files
https://www.hackster.io/kutluhan-aktar/ai-based-aquatic-ultrasonic-imaging-chemical-water-testing-f6b233#code
Schematics
PCB
https://www.hackster.io/kutluhan-aktar/ai-based-aquatic-ultrasonic-imaging-chemical-water-testing-f6b233#schematics
Documentation
Custom parts and enclosures
Models (Ridge Classifier and NVIDIA TAO RetinaNet)
https://www.hackster.io/kutluhan-aktar/ai-based-aquatic-ultrasonic-imaging-chemical-water-testing-f6b233#cad
Comments
Only logged in users can leave comments