About me
home
Portfolio
home

6조 09/26 발표

발표자
날짜
2023/09/26
교수님 피드백

1. 자율 주행

1.1 앞으로 할 일

코드로 로봇 구동하기
로봇에 내장된 센서에서 값 받아보기

2. 센서 제어

2.1 아두이노 블루투스 모듈(HM-10)

기존 HC-06 모듈보다 더 다양한 기능을 사용할 수 있는 HM-10 블루투스 모듈을 사용함
사용 목적 : 사람과 멀리 떨어지면 안내로봇이 기다리기 기능
아두이노 보드로 마스터 - 슬레이브 연결로 블루투스 통신 가능
동작 전압 : 2.5V ~ 3.3V
통신 거리 : 약 10m (HC-06) → 40m(HM-10)
baud rate : 1200 ~ 115200bps
사용 전류 : ~50mA
HC-06의 경우 안드로이드 핸드폰만 연결 가능했지만, HM-10인 경우 아이폰도 가능하다.

2.2 아두이노 환경

2.3 소스 코드

master
#include <SoftwareSerial.h> SoftwareSerial bluetooth(2, 3); // RX, TX void setup() { Serial.begin(9600); bluetooth.begin(9600); delay(1000); sendATCommand("AT+RENEW"); delay(1000); sendATCommand("AT"); delay(1000); sendATCommand("AT+ROLE1"); delay(1000); sendATCommand("AT+IMME1"); delay(3000); sendATCommand("AT+CON10CEA9FCDF5B"); } void loop() { sendATCommand("AT+RSSI?"); delay(10); } void sendATCommand(String command) { bluetooth.print(command); delay(500); while (bluetooth.available()) { char c = bluetooth.read(); Serial.print(c); } Serial.print("\r\n"); }
Arduino
복사
slave
#include <SoftwareSerial.h> SoftwareSerial bluetooth(2,3); // RX, TX void setup() { //기본 통신속도는 9600입니다. Serial.begin(9600); bluetooth.begin(9600); delay(1000); // 명령 실행에 충분한 시간을 줍니다. sendATCommand("AT+RENEW"); delay(1000); sendATCommand("AT"); delay(1000); sendATCommand("AT+ROLE0"); delay(7000); // sendATCommand("AT+IMME1"); // delay(1000); // sendATCommand("AT+ADDR?"); } void loop() { if (bluetooth.available()) { Serial.write(bluetooth.read()); } if (Serial.available()) { bluetooth.write(Serial.read()); } } void sendATCommand(String command) { bluetooth.print(command); delay(500); // 명령 실행에 충분한 시간을 줍니다. //Serial.print("hi1"); while (bluetooth.available()) { char c = bluetooth.read(); //Serial.print("hi2"); Serial.print(c); } Serial.print("\r\n"); }
Arduino
복사

2.4 노이즈 제거

Kalman filter(정지 상태에서 노이즈 제거, sampling 주기: 500ms)
Kalman filter(운동 상태에서 노이즈 제거, 일반적인 걸음 속도, 출발 지점과 도착 지점 왕복, sampling 주기 : 10ms)
Kalman filter의 경우 전체적인 추세를 잘 추종하지 못함 → 예측 모델에 대한 노이즈(0.1)에 대한 파라미터 값을 변화 시킴
예측 모델에 대한 노이즈를 증가시키면 센서 값에 더 fitting이 되지만 예측 모델에 대한 노이즈가 증가하는 단점이 있다. 아래 그림은 예측 모델에 대한 노이즈를 0.1 → 0.5로 증가시킨 그림이다.
칼만 필터는 이전 상태만 저장하면 되기 때문에 실시간으로 센서 정보를 활용할 때 좋다.
moving average filter
정지 상태에서 moving average filter(sampling 주기: 500ms)
이동 상태에서 moving average filter, 평균에 사용할 데이터 갯수 = 15개(sampling 주기 : 10ms)
아래 그림은 초기 부분을 제외하고 나머지 데이터를 plot한 그림이다. (필터 계산은 위 그림과 동일한 과정으로 진행하고 plot할 때만 출력을 진행 한함)
moving average filter 경우 초기에는 오차가 발생하지만 데이터가 누적이 되면서 데이터의 전체적인 추세를 잘 추종하는 것을 파악함(평균에 사용되는 데이터의 갯수 + 5 이후부터 어느 정도 데이터를 잘 추종함)
moving average filter 특징
데이터가 누적되면 noise 제거 성능이 올라간다.
급격한 변화에 잘 대응하지 못한다.

2.5 rssi 수신 세기에 따른 거리 계산

2.6 앞으로 할 일

블루투스 세기에 따른 거리 계산 코드 구현
real-time으로 거리를 계산하도록 시스템 구축
실제 블루투스 테스트

3. 음성 AI

학식 메뉴 웹 스크래핑

서울시립대학교의 각 건물에서 매주 업로드되는 학식 메뉴를 웹 스크래핑하기 위해 파이썬의 Selenium 라이브러리를 활용하였다. 이 작업의 시작 단계는 서울시립대학교 학식 웹페이지(https://www.uos.ac.kr/food/placeList.do)에 접근하는 것이다. Selenium의 웹드라이버를 사용하여 웹 페이지를 Chrome 브라우저로 해당 웹페이지를 자동으로 실행하고 버튼 클릭기능과 table text 읽기 기능을 사용하여 식단 정보를 수집하였다.
Local 환경에서 웹 스크래핑 코드 실행 영상
웹 페이지에 접근한 후에는 각 건물, 즉 학생회관, 자연과학관, 본관, 그리고 양식당(아느칸)에서 제공하는 메뉴를 찾기 위해 HTML 요소를 탐색하였다. 일반적으로 이러한 정보는 테이블 또는 리스트 형태로 웹 페이지에 표시되므로, table내의 각 요소들의 type을 정확하게 식별할 수 있어야 한다.
정확한 HTML 요소를 찾은 후에는 Selenium의 find_element_by_id, find_elements_by_xpath 등의 메소드를 사용하여 학식 메뉴에 대한 정보를 추출하였다. 모든 건물의 학식 메뉴 정보를 수집한 후에는 이를 효율적으로 저장하기 위해 데이터베이스 폴더 속 CSV 파일에 저장하였다. 이렇게 저장된 데이터는 Langchain의 Vector DataBase 저장시에 활용될 수 있다.

웹스크래핑 구현 코드

from selenium import webdriver from selenium.webdriver.common.by import By import pandas as pd import re from datetime import datetime, timedelta import os import glob class UOSMenuScraper: def __init__(self, save_dir='DB'): self.driver = webdriver.Chrome() self.buildings = { 'tab11': '학생회관', 'tab12': '본관', 'tab13': '양식당(아느칸)', 'tab14': '자연과학관' } self.current_weekday = datetime.now().weekday() self.current_date = datetime.now().strftime("%Y%m%d") self.save_dir = save_dir if not os.path.exists(self.save_dir): os.makedirs(self.save_dir) def init_driver(self): self.driver.get("https://www.uos.ac.kr/food/placeList.do") self.driver.implicitly_wait(10) def search_building(self, building_id): button = self.driver.find_element(By.ID, building_id) button.click() self.driver.implicitly_wait(1) def enter_weekly_menu(self): button = self.driver.find_element(By.ID, 'tab2') button.click() self.driver.implicitly_wait(1) def extract_meal_data(self): table_element = self.driver.find_element(By.XPATH, "//*[@id='week']/table/tbody") rows = table_element.find_elements(By.TAG_NAME, "tr") weekdays = ['월', '화', '수', '목', '금'] data = {'Building': [], 'Day': [], 'Weekday': [], 'Morning': [], 'Lunch': [], 'Dinner': []} for i, row in enumerate(rows): ths = row.find_elements(By.TAG_NAME, "th") tds = row.find_elements(By.TAG_NAME, "td") day = ths[0].text if ths else None weekday = weekdays[i] meals = [re.sub('<br>', ' ', td.get_attribute("innerHTML")).strip() for td in tds] if day: data['Building'].append(self.building_name) data['Day'].append(day.split(' ')[0]) data['Weekday'].append(weekday) meals = ['Not Provided' if len(meal) == 0 else meal for meal in meals] data['Morning'].append(meals[0] if len(meals) > 0 else None) data['Lunch'].append(meals[1] if len(meals) > 1 else None) data['Dinner'].append(meals[2] if len(meals) > 2 else None) return data def remove_old_files(self): for f in glob.glob(f"{self.save_dir}/*weekly_menu.csv"): os.remove(f) def save_to_csv(self, data): monday_date = datetime.now() - timedelta(days=self.current_weekday) friday_date = monday_date + timedelta(days=4) filename = f"{monday_date.strftime('%y%m%d')}-{friday_date.strftime('%y%m%d')}_{self.building_name}_weekly_menu.csv" full_path = os.path.join(self.save_dir, filename) df = pd.DataFrame(data) df.to_csv(full_path, index=False, encoding='utf-8') # df.to_csv(full_path, index=False, encoding='utf-8-sig') # 윈도우 환경 한글 깨짐 방지 def run(self): self.init_driver() self.remove_old_files() for building_id, building_name in self.buildings.items(): self.building_name = building_name self.search_building(building_id) self.enter_weekly_menu() meal_data = self.extract_meal_data() self.save_to_csv(meal_data) self.driver.implicitly_wait(10) self.driver.quit() if __name__ == '__main__': scraper = UOSMenuScraper(save_dir='DB') scraper.run()
Python
복사

초기 설정과 드라이버 초기화

__init__ 메소드에서는 초기 설정을 한다. 웹 드라이버로는 Chrome을 사용하며, 대상 건물 이름과 해당 건물의 탭 ID를 딕셔너리로 저장한다. 또한 현재의 요일과 날짜 정보, 그리고 데이터를 저장할 디렉토리를 설정한다.
init_driver 메소드에서는 Selenium 웹드라이버를 초기화하고, 대학의 학식 페이지에 접근한다. 페이지 로딩을 위해 명시적 대기를 사용한다.

건물과 주간메뉴 탭 선택

search_building 메소드에서는 각 건물의 탭을 클릭하는 작업을 수행한다. enter_weekly_menu 메소드에서는 주간 메뉴 탭(tab2)을 클릭한다.

메뉴 데이터 추출

extract_meal_data 메소드에서는 주간 메뉴 데이터를 추출한다. 이를 위해 XPATH를 사용하여 테이블의 tbody를 찾은 후, 각 행(tr)과 그 안의 셀(td)을 순회한다. 각 셀에서 메뉴 정보를 추출하고, 정규표현식을 사용하여 불필요한 문자를 제거한다.

파일 관리 및 데이터 저장

remove_old_files 메소드에서는 이전에 저장된 CSV 파일을 삭제한다. save_to_csv 메소드에서는 수집한 데이터를 Pandas DataFrame 형태로 CSV 파일에 저장한다.

실행

run 메소드에서는 위의 모든 메소드를 순차적으로 호출한다. 먼저 웹드라이버를 초기화하고, 이전 파일을 삭제한다. 그리고 각 건물에 대해 주간 메뉴 데이터를 추출하여 CSV 파일로 저장한다. 마지막으로 웹드라이버를 종료한다.