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 파일로 저장한다. 마지막으로 웹드라이버를 종료한다.