본문 바로가기
포트폴리오/개인 자작

긴급 구조 SOS 시스템 개발 프로젝트

by jamong1014 2023. 12. 1.
반응형

결과물

하드웨어 및 소프트웨어


기술개발의 개요

  • 아두이노 GPS Tracker와 충격센서를 접목시켜 응급상황에 신속히 대처할 수 있는 IOT 제품 개발.
  • Gps Tracker와 충격센서를 자동차에 내장시키고 특정 기준치 이상의 충격이 발생했을 때 운전자(환자)의 심박수 센서, 또는 적외선 온도 센서 등을 작동시켜 각종 센서 데이터를 사전에 구축한 사이트(긴급 구조 시스템)에 전송하여 사고 현황과 응급상황에 신속하게 대처할 수 있는 시스템.
  • 사용 기술 : HTML, CSS, JS(jquery, ajax), PHP(CURL, Google Map API), MySQL, C(아두이노)
  • 서버 : CAFE24 Web Hosting

시나리오

스토리 가정(서비스 상용화)

  1. 차량이 충돌사고가 났을 때 일정 이상의 충격량을 감지하면 차량에 탑재되어 있는 Gps Tracker가 작동
  2. 작동 후 운전자의 상태를 파악할 수 있는 Gps Tracker에 연결된 심박수 센서 또는 적외선 온도 센서, 차량의 위치(위도, 경도), 사고난 시간 등의 정보들을 사전에 구축한 사이트(긴급구조시스템)에 데이터 전송
  3. 사고 데이터를 받은 긴급구조 시스템은 사고 지점을 알 수 있으며 사고 지점으로부터 반경 5000m의 119 구급센터를 모두 검색하고 그 중에서 가장 가깝게 측정되는 구급센터로 경로 표시, 동시에 걸리는 시간과 거리도 실시간으로 표시
  4. 경로가 표시되는 동시에 표시된 119 구급센터로 자동으로 사고 관련 모든 데이터를 전송
  5. 즉 119 구급센터 또한 사고현황에 대해 바로 파악이 가능(전송된 데이터를 확인할 수 있는 119 구급센터 사이트로도 접속 가능)
  6. 운전자가 이 서비스를 이용하기 위해 등록한 자신의 인적사항을 충돌 사고가 났을 시 가져와 보다 더 신속한 조치를 취할 수 있음(병원 등록, 지인 연락 등등)
  7. 적외선 또는 심박수 센서를 통해 받아온 데이터를 통해 운전자의 상태 정보를 실시간으로 확인 가능(그래프 형식으로도 제작)
  8. 또한 119 구급센터에서 출발한 구급차의 위치도 알 수 있음(이 부분도 Gps Tracker가 필요하지만 비용적인 부담이 있어 그냥 임의로 위치 지정)  

Hardware

 

  • 아두이노 보드 중 ESP8266이라는 보드를 사용
  • 와이파이 모듈이 탑재 되어있는 보드이기에 주변 AP 신호를 스캔하여 연결되어 있는 센서들의 데이터를 내가 원하는 곳에 전송 가능 (센서 : 충격감지 센서, 적외선 온도 센서 등)
  • 해당 보드에서 사용하고 있는 버전과 심박수 센서의 기본 라이브러리가 호환되지 않아 적외선 온도센서로 대체
  • 실제로 상용화 된다면 LTE 데이터 신호를 사용할 수 있음
  • 케이스는 3D 프린터로 맞춤 제작

Source

#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <Adafruit_MLX90614.h>
#include <Wire.h>

const int SHOCK_PIN = 16;  // 충격 센서 핀 설정

Adafruit_MLX90614 mlx = Adafruit_MLX90614();

const int LED_PIN = 5;

byte ledStatus = LOW;
int status = WL_IDLE_STATUS;

// 와이파이 클라이언트 이용;
HTTPClient client;

const char mapHost[] = "http://gps.foolblack.com/api/index.php";
const int port = 80;

// GPS device SoftwareSerial 설정
static const int RXPin = 12, TXPin = 13;
static const uint32_t GPSBaud = 115200;

// The TinyGPS++ object
TinyGPSPlus gps;

// The serial connection to the GPS device
SoftwareSerial ss(RXPin, TXPin);

enum eStates
{
  EScan,
  EConnect,
  ESend
};

eStates state = EScan;

// DHT sensor.
const char* ssid = "jaymon";
const char* password = "93848234";
String host = "http://gps.foolblack.com";

const long interval = 5000;
unsigned long previousMillis = 0;
int shockValue = LOW; // 초기값 설정
const long wifiScanCooldown = 0; // Wi-Fi 스캔 쿨다운 시간 (0초)
unsigned long lastWifiScanMillis = 0;

WiFiServer server(80);

HTTPClient http;

void setup() {
  Serial.begin(115200);
  ss.begin(GPSBaud);
  pinMode(LED_PIN, OUTPUT);
  pinMode(SHOCK_PIN, INPUT);

  digitalWrite(LED_PIN, HIGH); // Write LED high/low
  delay(1000);
  digitalWrite(LED_PIN, LOW); // Write LED high/low
  Serial.println("Setup done");

  // DHT sensor
  Wire.begin(4, 5);
  mlx.begin();
}

void loop() {
  // main statechart loop
  delay(1000);

  // 충격 센서의 입력을 읽어옴
  shockValue = digitalRead(SHOCK_PIN);

  unsigned long currentMillis = millis();

  switch (state)
  {
    case EScan:
      if (shockValue == HIGH) {
        Serial.println("Shock detected! Scanning for WiFi...");
        if (currentMillis - lastWifiScanMillis >= wifiScanCooldown) {
          if (scan_Wifi()) {
            state = ESend;
            lastWifiScanMillis = currentMillis;
          }
        }
      }
      break;

    case EConnect:
      if (connectServer())
        state = ESend;
      break;

    case ESend:
      // gpsdata를 가져와서 서버로 보냄
      if (!SendToServer())
        state = EScan;
      break;
  }
}

bool dhtserver() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;

    double ambientTemp = mlx.readObjectTempC();

    String phpHost = host + "/api/insert2.php?temp=" + mlx.readObjectTempC();

    Serial.print("Connect to ");
    Serial.println(phpHost);

    Serial.print(ambientTemp);
    Serial.println("*C");

    client.begin(phpHost);

    client.setTimeout(1000);
    int httpCode2 = client.POST(phpHost);

    if (httpCode2 > 0) {
      Serial.printf("GET code : %d\n\n", httpCode2);

      if (httpCode2 == HTTP_CODE_OK) {
        String payload = client.getString();
        Serial.println(payload);
      }
    } else {
      Serial.printf("POST failed, error: %s\n", client.errorToString(httpCode2).c_str());
    }
  }
  client.end();
}

bool connectServer() {
  Serial.print("connecting to ");
  Serial.println(mapHost);
  Serial.println("Connected");
  return true;
}

bool scan_Wifi() {
  const char android[] = "jaymon"; // AP SSID
  const char pwd[] = "93848234";   // SSID PASS

  // WiFi를 스테이션 모드로 설정하고 이전에 연결된 AP가 있는 경우 연결 해제
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();

  delay(100);
  Serial.println("scan start");

  // WiFi.scanNetworks will return the number of networks found
  int n = WiFi.scanNetworks();
  Serial.println("scan done");
  if (n == 0)
    Serial.println("no networks found");
  else {
    Serial.print(n);
    Serial.println(" networks found");
    for (int i = 0; i < n; ++i) {
      Serial.print(i + 1);
      Serial.print(": ");
      Serial.print(WiFi.SSID(i));
      Serial.print(" (");
      Serial.print(WiFi.RSSI(i));
      Serial.print(")");
      Serial.println((WiFi.encryptionType(i) == ENC_TYPE_NONE) ? " " : "*");

      char ssid[100];
      WiFi.SSID(i).toCharArray(ssid, sizeof(ssid));

      if (WiFi.encryptionType(i) == ENC_TYPE_NONE || strcmp(ssid, android) == 0) {
        delay(100);
        if (WiFi.encryptionType(i) == ENC_TYPE_NONE) {
          Serial.print("Connecting to open SSID:");
          Serial.println(ssid);
          WiFi.begin(ssid);
        } else
          WiFi.begin(android, pwd);

        int connectLoop = 0;
        yield();

        while (WiFi.status() != WL_CONNECTED) {
          digitalWrite(LED_PIN, ledStatus);
          ledStatus = (ledStatus == HIGH) ? LOW : HIGH;
          delay(100);

          if (connectLoop > 100) {
            Serial.print("Timeout connecting to: ");
            Serial.println(ssid);
            digitalWrite(LED_PIN, LOW);
            break;
          }

          connectLoop++;
        }

        delay(1000);
        if (WiFi.status() == WL_CONNECTED) {
          Serial.print("Connected to ");
          Serial.println(ssid);
          return true;
        } else {
          Serial.println("Connection failed.");
          return false;
        }
      }
    }
  }
  Serial.println("");
  return false;
}

bool SendToServer() {
  // start feeding gps
  while (true) {
    smartDelay(100);
    String PostData = Send_Location_Time();
    client.begin(mapHost);
    client.addHeader("Content-Type", "application/x-www-form-urlencoded");

    unsigned long start = millis();
    int interval = 3000;
    yield();
    int httpCode = client.POST(PostData);

    if (millis() - start > interval) {
      // no response from server
      client.end();
      return false;
    }

    Serial.print("Server response: ");
    Serial.println(httpCode);
    smartDelay(100);
    Serial.println(PostData);
    delay(100);
    start = millis();
    client.POST(PostData);

    dhtserver();

    yield();
  }

  client.end();
  return true;
}

String Send_Location_Time() {
  uint8_t mac[WL_MAC_ADDR_LENGTH];
  WiFi.macAddress(mac);
  String macAddress = "";
  for (int i = 0; i < WL_MAC_ADDR_LENGTH; i++)
    macAddress += String(mac[i], HEX);

  String retval = "device_id=";
  retval += macAddress;
  retval += "&date_and_time=";
  if (gps.date.isValid()) {
    retval += gps.date.year();
    retval += "-";
    retval += gps.date.month();
    retval += "-";
    retval += gps.date.day();
    retval += " ";
  } else
    retval += "invalid ";

  if (gps.time.isValid()) {
    retval += gps.time.hour();
    retval += ":";
    retval += gps.time.minute();
    retval += ":";
    retval += gps.time.second();
  } else
    retval += "invalid";

  if (gps.location.isValid()) {
    char buf[12];
    retval += "&latitude=";
    dtostrf(gps.location.lat(), 4, 6, buf);
    retval += buf;
    retval += "&longitude=";
    dtostrf(gps.location.lng(), 4, 6, buf);
    retval += buf;
  } else
    retval += "&latitude=invalid&longitude=invalid";

  return retval;
}

static void smartDelay(unsigned long ms) {
  unsigned long start = millis();
  do {
    while (ss.available())
      gps.encode(ss.read());
    yield(); // give others some time..
  } while (millis() - start < ms);
}

// 1초에 한 번 GPS 데이터를 읽고 추출
void gps_loop() {
  while (ss.available()) {
    gps.encode(ss.read());
    delay(1000);
    yield();
  }
}

 

*코드가 살짝 난잡할 수 있음


Software

 

GPS TRAKER

 

gps.foolblack.com

  • 데이터를 모니터링 할 수 있는 시스템

 

로그인 및 회원가입 폼

 

충돌 사고 전 대기화면

  • 대기화면
  • 충돌 사고가 나면 데이터가 긴급구조시스템에 전송되면서 해당 화면 메시지 폼을 띄움
  • 메시지 폼 : Mac 주소 / 사고 시간 / 사고 위치(위도, 경도)
  • 사고가 난 제품의 Mac 주소가 자동으로 'Device Number' 에 기입
  • 동시에 차량을 모니터링 할 수 있는 사이트로 리다이렉트

 

메인 폼/Status 폼
119 구급센터에 전송된 사고 현황 데이터 / 클라이언트와 서버 관리자간의 채팅

  • 해당 차량 제품의 Mac 주소를 기점으로 사고 난 날짜와, 사고 관련 데이터를 불러옴
  • 사고지점 B 마커가 GPS 모듈을 통해 받아온 위치
  • 사고 난 위치를 기점으로 반경 5000m 안에 있는 119 구급센터를 모두 검색하고 Haversine Formula(하버사인 공식)을 통해 가장 가깝게 측정되는 구급 센터로 동선 표기
  • 표기됨과 동시에 걸리는 시간과 거리를 실시간으로 표시 (Google Map API를 사용하였기에 국내에서는 대중교통 수단밖에 지원 안됨)
  • 오른쪽에 있는 정보는 운전자의 인적사항 정보
  • Body Temp 부분이 운전자의 상태를 실시간을 보여주는 데이터
  • 왼쪽 하단에 Status 버튼을 눌러 두 번째 폼으로 변환
  • 동선이 표기됨과 동시에 데이터를 받은 119 구급센터는 구급차를 이송
  • 이송된 구급차의 위치를 확인 할 수 있음(구급차의 위치는 임의로 지정)
  • 지정된 구급차의 위치부터 사고난 지점까지의 남은 거리와 시간을 실시간으로 표시
  • 왼쪽 하단의 로그 기록은 구급차의 지나온 위치 로그 기록
  • 오른쪽은 운전자의 적외선 온도 상태 변화를 좀 더 상세히 보기 위해 제작된 그래프 폼
  • 119 구급센터에 전송된 사고 관련 현황 데이터를 볼 수 있음(119 구급센터 전산망)
  • 클라이언트와 서버 관리자 간의 채팅

개선할 점

  • 상용화 할 시 트래픽이 많이 몰릴 것이 예상되어 고가용성과, 로드밸런싱 솔루션을 추가(AWS로 서버 이전)
  • 해당 Google Map API는 정보보호 법 때문에 대중교통 이외의 수단(자동차, 걷기)은 지원 불가 Naver map API로 사용
  • 구급차 Gps Tracker까지 구현
  • 보드 버전을 올려 심박수 센서까지 추가

활용 방안

  • 스마트워치와 연동하여 더 많은 센서의 데이터를 종합할 수 있음
  • 정상적인 차량 운행 중에도 운전자의 상태를 파악하여 과도한 스트레스나 피로감을 느끼고 있을 때 경고 알림을 보낼 수 있음
  • 다양한 센서에서 수집되는 데이터를 활용해 차량의 안전성과 편의성을 향상시키는 연구 및 개발에 활용될 수 있음 

 

반응형

댓글