Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
Sai Prakash Goud
Published © GPL3+

VisionAssist : Advanced Wearable for the Visually impaired

A smart wearable device for visually impaired individuals that detects obstacles and provides alerts, with an integrated camera to capture.

IntermediateFull instructions provided139
VisionAssist : Advanced Wearable for the Visually impaired

Things used in this project

Hardware components

Seeed Studio XIAO ESP32S3 Sense
Seeed Studio XIAO ESP32S3 Sense
×1
Blues Swan
Blues Swan
×1
Raspberry Pi 4 Model B
Raspberry Pi 4 Model B
×1
DFROBOT Mini Laser Distance Range sensor
×1
TTP223 Touch Key Module
×1
ERM Coin Vibration Motor
×1
1250 mAh 3.7V single cell Rechargeable LiPo Battery
×1
SanDisk ULTRA 8 GB MicroSD Card
×1
SanDisk Ultra 64 GB Micro SD Card
×1
Solderless Breadboard Full Size
Solderless Breadboard Full Size
×1

Software apps and online services

Arduino IDE
Arduino IDE
Raspbian
Raspberry Pi Raspbian
STM32CUBEPROG
STMicroelectronics STM32CUBEPROG

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free
10 Pc. Jumper Wire Kit, 5 cm Long
10 Pc. Jumper Wire Kit, 5 cm Long

Story

Read more

Schematics

XIAO Esp32 S3

Blues Swan

Code

Touch sensor-Based Image Capture and HTTP POST Code

C/C++
Touch-Based Image Capture and HTTP POST Code: An image will be captured when a touch is detected on the touch sensor. The captured image will be posted to an HTTP server and saved to an SD card.
#include <Arduino.h>
#include <WiFi.h>
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"

#include "esp_camera.h"
#include "FS.h"
#include "SD.h"
#include "SPI.h"

#define CAMERA_MODEL_XIAO_ESP32S3 // Has PSRAM

#include "camera_pins.h"

#define TOUCH_PIN 2 // GPIO where the OUT pin of the touch sensor is connected

const char* ssid = "Area21";
const char* password = "123459876";

String serverName = "192.168.141.122";

String serverPath = "/upload.php";     // The default serverPath should be upload.php

const int serverPort = 80;

WiFiClient client;

unsigned long lastCaptureTime = 0; // Last shooting time
int imageCount = 1;                // File Counter
bool camera_sign = false;          // Check camera status
bool sd_sign = false;              // Check sd status
bool touchDetected = false;        // Flag for touch sensor state

// Save pictures to SD card
void photo_save(const char *fileName) {
  // Take a photo
  camera_fb_t *fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("Failed to get camera frame buffer");
    return;
  }
  // Save photo to file
  writeFile(SD, fileName, fb->buf, fb->len);
  
  // Release image buffer
  esp_camera_fb_return(fb);

  Serial.println("Photo saved to file");
}

// SD card write file
void writeFile(fs::FS &fs, const char *path, uint8_t *data, size_t len) {
  Serial.printf("Writing file: %s\n", path);

  File file = fs.open(path, FILE_WRITE);
  if (!file) {
    Serial.println("Failed to open file for writing");
    return;
  }
  if (file.write(data, len) == len) {
    Serial.println("File written");
  } else {
    Serial.println("Write failed");
  }
  file.close();
}

void setup() {
  Serial.begin(115200);
  while (!Serial); // Wait for serial monitor to start


    WiFi.mode(WIFI_STA);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);  
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.println();
  Serial.print("ESP32-CAM IP Address: ");
  Serial.println(WiFi.localIP());

  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_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.frame_size = FRAMESIZE_UXGA;
  config.pixel_format = PIXFORMAT_JPEG; // for streaming
  config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
  config.fb_location = CAMERA_FB_IN_PSRAM;
  config.jpeg_quality = 12;
  config.fb_count = 1;
  
  // if PSRAM IC present, init with UXGA resolution and higher JPEG quality
  //                      for larger pre-allocated frame buffer.
  if (config.pixel_format == PIXFORMAT_JPEG) {
    if (psramFound()) {
      config.jpeg_quality = 10;
      config.fb_count = 2;
      config.grab_mode = CAMERA_GRAB_LATEST;
    } else {
      // Limit the frame size when PSRAM is not available
      config.frame_size = FRAMESIZE_SVGA;
      config.fb_location = CAMERA_FB_IN_DRAM;
    }
  } else {
    // Best option for face detection/recognition
    config.frame_size = FRAMESIZE_240X240;
#if CONFIG_IDF_TARGET_ESP32S3
    config.fb_count = 2;
#endif
  }

  // Camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }
  
  camera_sign = true; // Camera initialization check passes

  // Initialize SD card
  if (!SD.begin(21)) {
    Serial.println("Card Mount Failed");
    return;
  }
  uint8_t cardType = SD.cardType();

  // Determine if the type of SD card is available
  if (cardType == CARD_NONE) {
    Serial.println("No SD card attached");
    return;
  }

  Serial.print("SD Card Type: ");
  if (cardType == CARD_MMC) {
    Serial.println("MMC");
  } else if (cardType == CARD_SD) {
    Serial.println("SDSC");
  } else if (cardType == CARD_SDHC) {
    Serial.println("SDHC");
  } else {
    Serial.println("UNKNOWN");
  }

  sd_sign = true; // SD initialization check passes

  Serial.println("XIAO ESP32S3 Sense Camera Image Capture");
  Serial.println("Touch the sensor to capture an image");

  pinMode(TOUCH_PIN, INPUT_PULLUP); // Configure the touch pin
}

void loop() {
  if (camera_sign && sd_sign) {
    int touchState = digitalRead(TOUCH_PIN);

    // Check if the touch sensor is pressed
    if (touchState == LOW) { // Assuming LOW when touched
      if (!touchDetected) { // Avoid multiple captures for a single press
        touchDetected = true;

        sendPhoto();          // When touch is captured image will be uploaded to http server

        char filename[32];
        sprintf(filename, "/image%d.jpg", imageCount);
        photo_save(filename);
        Serial.printf("Saved picture: %s\n", filename);
        imageCount++;
      }
    } else {
      touchDetected = false; // Reset flag when touch is no longer detected
    }
  }
}



String sendPhoto() {
  String getAll;
  String getBody;

  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();
  if(!fb) {
    Serial.println("Camera capture failed");
    delay(1000);
    ESP.restart();
  }
  
  Serial.println("Connecting to server: " + serverName);

  if (client.connect(serverName.c_str(), serverPort)) {
    Serial.println("Connection successful!");    
    String head = "--SmartWearable\r\nContent-Disposition: form-data; name=\"imageFile\"; filename=\"smartwearable.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n";
    String tail = "\r\n--SmartWearable--\r\n";


    uint32_t imageLen = fb->len;
    uint32_t extraLen = head.length() + tail.length();
    uint32_t totalLen = imageLen + extraLen;
  
    client.println("POST " + serverPath + " HTTP/1.1");
    client.println("Host: " + serverName);
    client.println("Content-Length: " + String(totalLen));
    client.println("Content-Type: multipart/form-data; boundary=SmartWearable");
    client.println();
    client.print(head);
  
    uint8_t *fbBuf = fb->buf;
    size_t fbLen = fb->len;
    for (size_t n=0; n<fbLen; n=n+1024) {
      if (n+1024 < fbLen) {
        client.write(fbBuf, 1024);
        fbBuf += 1024;
      }
      else if (fbLen%1024>0) {
        size_t remainder = fbLen%1024;
        client.write(fbBuf, remainder);
      }
    }   
    client.print(tail);
    
    esp_camera_fb_return(fb);
    
    int timoutTimer = 10000;
    long startTimer = millis();
    boolean state = false;
    
    while ((startTimer + timoutTimer) > millis()) {
      Serial.print(".");
      delay(100);      
      while (client.available()) {
        char c = client.read();
        if (c == '\n') {
          if (getAll.length()==0) { state=true; }
          getAll = "";
        }
        else if (c != '\r') { getAll += String(c); }
        if (state==true) { getBody += String(c); }
        startTimer = millis();
      }
      if (getBody.length()>0) { break; }
    }
    Serial.println();
    client.stop();
    Serial.println(getBody);
  }
  else {
    getBody = "Connection to " + serverName +  " failed.";
    Serial.println(getBody);
  }
  return getBody;
}

Obstacle Detection

C/C++
Obstacle detection is performed using a DFROBOT Mini Laser Distance Range Sensor. If an obstacle is detected, the person will be alerted by a vibration motor.
#include "Wire.h"
#define address 0x74

uint8_t buf[2] = { 0 };

void setup() {
  Serial.begin(115200);
  pinMode(6, OUTPUT);
  Wire.begin();
}

uint8_t dat = 0xB0;
int distance = 0;


void loop() {
  writeReg(0x10, &dat, 1);
  delay(50);
  readReg(0x02, buf, 2);
  distance = buf[0] * 0x100 + buf[1] + 10;
  Serial.print("distance=");
  Serial.print(distance);
  Serial.print("mm");
  Serial.println("\t");

  if(distance < 750)
  {
     digitalWrite(6, HIGH);
    delay(100);
    Serial.println("ALERT");
    digitalWrite(6, LOW);
  }
  delay(100);
}

uint8_t readReg(uint8_t reg, const void* pBuf, size_t size) {
  if (pBuf == NULL) {
    Serial.println("pBuf ERROR!! : null pointer");
  }
  uint8_t* _pBuf = (uint8_t*)pBuf;
  Wire.beginTransmission(address);
  Wire.write(&reg, 1);
  if (Wire.endTransmission() != 0) {
    return 0;
  }
  delay(20);
  Wire.requestFrom(address, (uint8_t)size);
  for (uint16_t i = 0; i < size; i++) {
    _pBuf[i] = Wire.read();
  }
  return size;
}

bool writeReg(uint8_t reg, const void* pBuf, size_t size) {
  if (pBuf == NULL) {
    Serial.println("pBuf ERROR!! : null pointer");
  }
  uint8_t* _pBuf = (uint8_t*)pBuf;
  Wire.beginTransmission(address);
  Wire.write(&reg, 1);

  for (uint16_t i = 0; i < size; i++) {
    Wire.write(_pBuf[i]);
  }
  if (Wire.endTransmission() != 0) {
    return 0;
  } else {
    return 1;
  }
}

Server_Gallery_Code

PHP
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Smart Wearable Gallery</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      background-color: #f4f4f4;
      color: #333;
      margin: 0;
      padding: 0;
    }

    header {
      background-color: #333;
      color: #fff;
      padding: 10px 20px;
      text-align: center;
      position: relative;
      overflow: hidden;
      white-space: nowrap;
    }

    header::before {
      content: "Buil2gether 2.0";
      position: absolute;      top: 0;
      left: 100%;
      height: 100%;
      font-size: 24px;
      line-height: 40px;
      animation: scrollText 30s linear infinite;
      color: #4CAF50;
    }

    @keyframes scrollText {
      0% { left: 100%; }
      100% { left: -100%; }
    }

    h2 {
      text-align: center;
      margin-top: 20px;
    }

    .flex-container {
      display: flex;
      flex-wrap: wrap;
      justify-content: center;
      padding: 20px;
    }

    .flex-container > div {
      background-color: #fff;
      box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
      margin: 15px;
      text-align: center;
      border-radius: 8px;
      overflow: hidden;
      transition: transform 0.2s;
    }

    .flex-container > div:hover {
      transform: scale(1.05);
    }

    img {
      max-width: 200px; /* Adjusted size to make images smaller */
      height: auto;
      display: block;
      border-bottom: 1px solid #ddd;
    }

    p {
      margin: 0;
      padding: 10px;
      font-size: 14px;
      background-color: #f4f4f4;
    }

    a {
      text-decoration: none;
      color: #4CAF50;
    }

    a:hover {
      color: #333;
    }
  </style>
</head>
<body>
  <header>
    <span>Build2gether 2.0</span>
  </header>
  <h2>Smart Wearable Gallery</h2>

  <?php
    $image_extensions = array("png","jpg","jpeg","gif");

    if(isset($_GET["delete"])){
      $imageFileType = strtolower(pathinfo($_GET["delete"],PATHINFO_EXTENSION));
      if (file_exists($_GET["delete"]) && in_array($imageFileType, $image_extensions)) {
        echo "File found and deleted: " .  $_GET["delete"];
        unlink($_GET["delete"]);
      } else {
        echo 'File not found - <a href="gallery.php">refresh</a>';
      }
    }

    $dir = 'uploads/';
    if (is_dir($dir)){
      echo '<div class="flex-container">';
      $count = 1;
      $files = scandir($dir);
      rsort($files);
      foreach ($files as $file) {
        if ($file != '.' && $file != '..') { ?>
          <div>
            <p><a href="gallery.php?delete=<?php echo $dir . $file; ?>">Delete file</a> - <?php echo $file; ?></p>
            <a href="<?php echo $dir . $file; ?>">
              <img src="<?php echo $dir . $file; ?>" alt="<?php echo $file; ?>" title="<?php echo $file; ?>"/>
            </a>
          </div>
  <?php
          $count++;
        }
      }
    }
    if($count==1) { echo "<p>No images found</p>"; } 
  ?>
  </div>
</body>
</html>

Credits

Sai Prakash Goud

Sai Prakash Goud

1 project • 1 follower

Comments