과제 목적
학교에서 분리수거가 제대로 이루어지지 않아 재활용품이 일반 쓰레기와 섞이는 문제가 자주 발생하고 있다.
특히 캔, 플라스틱, 종이 등이 정확히 분류되지 않아 재활용 효율이 크게 떨어지고 있다.
이를 해결하기 위해 ‘AI 스마트 쓰레기통’을 제작하고자 했다.
웹캠으로 투입되는 쓰레기를 촬영하면 티처블 머신이 일반쓰레기, 플라스틱, 캔 중 해당 쓰레기의 종류를 분류하고, 그 결과를 마이크로비트에 전송하면 마이크로비트는 플라스틱일 때 서브모터를 회전시켜 입구가 열리게 하는 쓰레기통이다.
최종 프로젝트
•
하드웨어 구현
쓰레기통처럼 보이도록 간단한 하드웨어를 만들었다. 특히 서브모터를 통해 열리고 닫힐 뚜껑을 구현하였다.
•
Teachable Machine에서 class1, 2, 3에 각각 일반쓰레기, 플라스틱, 캔에 대한 데이터를 넣고 학습시켰다.
•
처음에는 Teachable Machine에서 분류된 결과를 마이크로비트로 전달하기 위해 유선 연결 방식을 활용하려고 하였다. 그러나 마이크로비트를 웹과 직접 연결해주는 기존 웹 기반 시리얼 통신 사이트가 정상적으로 작동하지 않는 문제가 발생하였다. 특히, 분류 결과를 실시간 전송해야 하는 프로젝트 특성상 안정적인 연결이 필요했지만, 해당 사이트에서 마이크로비트를 인식하지 못하거나 데이터가 전달되지 않는 오류가 반복되었다.
•
이에 따라 통신 방식을 유선에서 Bluetooth로 전환하는 방향으로 변경하였다.
무선 방식으로 전환한 뒤, 작동 웹사이트를 코드펜으로 구현하는 코드를 챗gpt를 이용해 구현하였다.
Teachable Machine 결과가 90% 이상일 때만 Class 1 → "1", Class 2 → "2", Class 3 → "3" 형태로 블루투스로 신호를 전송하였다.
•
마이크로비트 코드는 전 수업에 사용했던 코드를 사용하였다. 플라스틱으로 인식될 경우에만 뚜껑이 열리도록 서브모터를 조작하였다. 기존 코드에서 1, 2, 3의 값을 받았을 때 서브모터의 작동 부분만 다음과 같이 수정하였다.
프로젝트 코드
코드펜 코드
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>Teachable Machine + micro:bit Bluetooth Controller</title>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@latest/dist/tf.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@teachablemachine/image@latest/dist/teachablemachine-image.min.js"></script>
<style>
body{font-family:'Pretendard','Noto Sans KR',sans-serif;display:flex;flex-direction:column;align-items:center;
background:linear-gradient(135deg,#dfe9f3,#ffffff);min-height:100vh;padding:1rem;}
h1{margin:0.5rem;font-size:1.6rem;font-weight:700;}
#status{margin:0.5rem;font-weight:600;}
#sent,#received{
margin:.3rem 0;padding:.4rem .8rem;border-radius:6px;background:#f5f5f5;
font-size:0.95rem;width:260px;text-align:left;}
#sent{border-left:4px solid #4caf50;}
#received{border-left:4px solid #2196f3;}
button{margin:.3rem;padding:.6rem 1.2rem;border:none;border-radius:10px;background:#4caf50;color:white;font-weight:bold;cursor:pointer;}
button:disabled{background:#ccc;cursor:not-allowed;}
canvas{border-radius:12px;box-shadow:0 0 10px rgba(0,0,0,0.2);margin:1rem;}
</style>
</head>
<body>
<h1>🎥 TM Class → micro:bit Bluetooth</h1>
<div id="status">미연결</div>
<div id="sent">HTML → micro:bit: –</div>
<div id="received">micro:bit → HTML: –</div>
<button id="connectButton">🔗 Connect</button>
<button id="disconnectButton" disabled>🔒 Disconnect</button>
<div id="webcam-container"></div>
<div id="label-container"></div>
<script>
/* =========================
Bluetooth 설정
============================*/
const UART_SERVICE_UUID='6e400001-b5a3-f393-e0a9-e50e24dcca9e';
let device,txChar,rxChar,isConnected=false,sending=false,sendQueue=[];
let lastSent = ""; // 마지막 전송 값
const statusEl=document.getElementById('status');
const sentEl=document.getElementById('sent');
const receivedEl=document.getElementById('received');
const btnConnect=document.getElementById('connectButton');
const btnDisc=document.getElementById('disconnectButton');
function logStatus(msg){statusEl.textContent=msg;}
function logSent(cmd){sentEl.textContent=`HTML → micro:bit: ${cmd}`;}
// 안전 전송 큐
async function safeSend(cmd){
if(cmd === lastSent) return; // 동일 값이면 전송 안함
lastSent = cmd;
sendQueue.push(cmd);
if(sending||!isConnected||!txChar)return;
sending=true;
while(sendQueue.length>0){
const next=sendQueue.shift();
const data=new TextEncoder().encode(next+'\n');
try{
if(txChar.properties.writeWithoutResponse) await txChar.writeValueWithoutResponse(data);
else await txChar.writeValue(data);
logSent(next);
await new Promise(r=>setTimeout(r,50));
}catch(e){
logStatus('⚠️ 전송 오류: '+e.message);
await new Promise(r=>setTimeout(r,200));
}
}
sending=false;
}
// Bluetooth 연결 버튼
btnConnect.addEventListener('click',async()=>{
try{
logStatus('🔍 micro:bit 검색 중…');
device=await navigator.bluetooth.requestDevice({
filters:[{namePrefix:'BBC micro:bit'}],
optionalServices:[UART_SERVICE_UUID]
});
const server=await device.gatt.connect();
const svc=await server.getPrimaryService(UART_SERVICE_UUID);
const chars=await svc.getCharacteristics();
chars.forEach(ch=>{
if((ch.properties.write||ch.properties.writeWithoutResponse)&&!txChar)txChar=ch;
if((ch.properties.notify||ch.properties.indicate)&&!rxChar)rxChar=ch;
});
rxChar.addEventListener('characteristicvaluechanged',e=>{
const v=new TextDecoder().decode(e.target.value).trim();
receivedEl.textContent=`micro:bit → HTML: ${v}`;
});
await rxChar.startNotifications();
isConnected=true;
btnConnect.disabled=true;
btnDisc.disabled=false;
logStatus('✅ 연결 완료');
}catch(e){logStatus('❌ 연결 실패: '+e.message);}
});
// Disconnect
btnDisc.addEventListener('click',()=>{
if(device?.gatt.connected)device.gatt.disconnect();
isConnected=false;
btnConnect.disabled=false;
btnDisc.disabled=true;
logStatus('🔌 연결 해제됨');
});
/* =========================
Teachable Machine 설정
============================*/
const URL = "https://teachablemachine.withgoogle.com/models/udgew2fu9/";
let model, webcam, labelContainer, maxPredictions;
async function initTM(){
const modelURL = URL + "model.json";
const metadataURL = URL + "metadata.json";
model = await tmImage.load(modelURL, metadataURL);
maxPredictions = model.getTotalClasses();
webcam = new tmImage.Webcam(300, 300, true);
await webcam.setup(); await webcam.play();
window.requestAnimationFrame(loop);
document.getElementById("webcam-container").appendChild(webcam.canvas);
labelContainer=document.getElementById("label-container");
for(let i=0;i<maxPredictions;i++) labelContainer.appendChild(document.createElement("div"));
}
async function loop(){
webcam.update();
await predict();
window.requestAnimationFrame(loop);
}
async function predict(){
const prediction = await model.predict(webcam.canvas);
let bestClass="", bestProb = 0;
for(let i=0;i<maxPredictions;i++){
labelContainer.childNodes[i].innerHTML =
prediction[i].className + ": " + prediction[i].probability.toFixed(2);
if(prediction[i].probability > bestProb){
bestProb = prediction[i].probability;
bestClass = prediction[i].className;
}
}
// --- Class → Bluetooth 숫자 전송 ---
if(bestProb > 0.90){ // 90% 이상일 때만 실행
if(bestClass === "Class 1") safeSend("1");
else if(bestClass === "Class 2") safeSend("2");
else if(bestClass === "Class 3") safeSend("3");
}
}
initTM();
</script>
</body>
</html>
HTML
복사
기타 자료
느낀점
스마트 쓰레기통을 구현하는 과정에서 하드웨어와 소프트웨어가 함께 작동하는 시스템이 어떻게 구성되는지 직접 경험할 수 있었다. 마이크로비트 키트를 이용해 모터 같은 물리적 장치를 제어하고, Teachable Machine으로 학습된 모델을 통해 쓰레기의 종류를 인식한 뒤 CodePen에서 웹 기반으로 연결해 동작을 구현하는 과정이 흥미롭고 재미있었다. 특히, 레고로 구조물을 조립하며 하드웨어를 구현하는 것이 가장 재밌었다. AI가 분류한 결과가 마이크로비트로 전달되어 실제로 뚜껑이 움직이는 모습을 보면서 추상적인 인공지능 모델이 실제 물리적 동작으로 연결되는 흐름을 눈으로 확인할 수 있었다.



