학교에서 분리수거가 제대로 이루어지지 않아 재활용품이 일반 쓰레기와 섞이는 문제가 자주 발생하고 있다.
특히 캔, 플라스틱, 종이 등이 정확히 분류되지 않아 재활용 효율이 크게 떨어지고 있다.
이를 해결하기 위해 ‘AI 스마트 쓰레기통’을 제작하고자 했다.
웹캠으로 투입되는 쓰레기를 촬영하면 티처블 머신이 일반쓰레기, 플라스틱, 캔 중 해당 쓰레기의 종류를 분류하고, 그 결과를 마이크로비트에 전송하면 마이크로비트는 플라스틱일 때 서브모터를 회전시켜 입구가 열리게 하는 쓰레기통이다.
https://www.canva.com/design/DAG5YoIpLc4/w0Hj2TGUDcHA5upB9zCQwA/edit?utm_content=DAG5YoIpLc4&utm_campaign=designshare&utm_medium=link2&utm_source=sharebutton



<!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>