다운로드 교육자료 문제해결 레퍼런스 구입방법

교육 자료 : 자바스크립트 : 로봇 청소기 흉내 내기

개요
로봇 청소기는 센서를 사용하여 장애 상황을 감지하고, 자율적인 판단으로 실내를 이동하며 청소합니다. 벽과의 거리를 감지하거나 주행 중 아래로 떨어지지 않도록 경계를 감지하고, 먼지의 양을 검출하기 위해 다양한 센서들을 갖추고 있습니다.

요즘의 로봇 청소기는 위치 인식 센서를 사용하여 계획된 경로를 따라 이동하지만 초기의 로봇 청소기가 이동하는 방식은 아주 단순하였습니다. 청소를 하면서 이리저리 움직이다가 벽이나 가구 등의 장애물을 만나면 장애물이 없는 쪽으로 방향을 바꾸는 방식입니다. 시간이 걸리고 같은 장소를 청소하기도 하지만 가격이 저렴하다는 장점이 있습니다.

장애물을 감지하는 방식은 민감한 터치 센서를 사용하여 직접 부딪쳐 보고 장애물을 감지하는 방식과 적외선 또는 초음파 센서를 사용하여 직접 부딪치지 않고서도 장애물을 감지하는 방식이 있습니다.

이제 햄스터 로봇을 사용하여 로봇 청소기처럼 동작하도록 만들어 봅시다.
벽을 피해 이동하기
벽이나 장애물을 만나면 무작위로 방향을 바꾼 후 앞으로 이동하면서 실내를 빠짐없이 청소하도록 합시다. 실험을 위해 적당한 높이의 책으로 둘레를 막아 거실을 꾸며 봅시다. 책은 가능한 두껍고 큰 것으로 하는 것이 좋습니다. 책 대신 A4 용지를 접어서 벽을 만들어도 되고 미로판의 벽을 사용하여도 됩니다.

햄스터 로봇이 벽을 감지하면 왼쪽이나 오른쪽으로 회전하여 벽과 부딪치지 않도록 해야 합니다. 햄스터 로봇의 오른쪽 근접 센서가 먼저 벽에 접근하면 왼쪽으로 회전하는 것이 벽에 부딪치지 않고 안전합니다. 반대로 왼쪽 근접 센서가 먼저 벽에 접근하면 오른쪽으로 회전하는 것이 좋습니다.

var hamster = Hamster.create();
var sensing = true;

function execute() {
    if(sensing) {
        if(hamster.leftProximity() > 50) { // 왼쪽 근접 센서가 벽을 감지하면
            sensing = false;
            hamster.wheels(30, -30); // 오른쪽으로 회전한다.
            setTimeout(function() {
                sensing = true;
            }, Math.random() * 1000);
        } else if(hamster.rightProximity() > 50) { // 오른쪽 근접 센서가 벽을 감지하면
            sensing = false;
            hamster.wheels(-30, 30); // 왼쪽으로 회전한다.
            setTimeout(function() {
                sensing = true;
            }, Math.random() * 1000);
        } else {
            hamster.wheels(30); // 앞으로 이동한다.
        }
    }
}

벽을 감지하는 판단 기준인 숫자 50과 회전하는 시간의 범위를 변경하면서 로봇의 이동 형태를 좀 더 발전시켜 봅시다.
청소 기능 추가하기
햄스터 로봇에 작은 청소기가 부착되어 있으면 청소 기능을 수행할 수 있겠지만, 작고 귀여운 햄스터 로봇에 실제로 청소기가 달려 있지는 않습니다. 햄스터 로봇의 양쪽 LED를 번갈아 깜박여서 청소하고 있다는 것을 표시하도록 합시다.

var hamster = Hamster.create();
var sensing = true;
var left = true;

function execute() {
    // 20msec마다 한 번씩 양쪽 LED를 번갈아 켠다.
    if(left) {
        hamster.leds(Hamster.LED_BLUE, 0);
    } else {
        hamster.leds(0, Hamster.LED_BLUE);
    }
    left = !left;

    if(sensing) {
        if(hamster.leftProximity() > 50) { // 왼쪽 근접 센서가 벽을 감지하면
            sensing = false;
            hamster.wheels(30, -30); // 오른쪽으로 회전한다.
            setTimeout(function() {
                sensing = true;
            }, Math.random() * 1000);
        } else if(hamster.rightProximity() > 50) { // 오른쪽 근접 센서가 벽을 감지하면
            sensing = false;
            hamster.wheels(-30, 30); // 왼쪽으로 회전한다.
            setTimeout(function() {
                sensing = true;
            }, Math.random() * 1000);
        } else {
            hamster.wheels(30); // 앞으로 이동한다.
        }
    }
}
가장자리 검출하기
문이 열려 있으면 햄스터 로봇 청소기가 마루 아래로 떨어질 염려가 있습니다. 바닥 센서를 사용하여 마루의 끝을 검출하고 마루의 끝을 벗어나지 않도록 합시다. 높은 곳에서 떨어지면 로봇이 부서질지 모르니 아래 그림과 같이 A4 용지에 검은색 테이프로 둘러서 경계를 만듭니다. 미리 제작된 파일을 프린터로 인쇄해도 됩니다. 바닥 센서가 마루를 벗어나면 반사된 빛이 없어 바닥 센서의 값이 0이 되는데, 검은색 선이 같은 역할을 하게 됩니다.

로봇 청소기 가장자리 실습판 내려 받기 PDF PPT

가장자리를 감지하여 벗어나지 않도록 하는 동작은 벽을 감지하여 피하는 경우와 비슷하기 때문에 앞에서 작성한 코드를 약간 수정하면 됩니다. 벽을 감지하는 경우와 다르게 잠시 뒤로 이동한 후에 방향을 바꾸는 이유는 그 자리에서 바로 돌면 바퀴가 마루의 가장자리에 걸릴 수 있기 때문입니다.

var hamster = Hamster.create();
var sensing = true;

function execute() {
    if(sensing) {
        if(hamster.leftFloor() < 20) { // 왼쪽 바닥 센서가 검은색 선 위에 있으면
            sensing = false;
            hamster.wheels(-30); // 0.5초 뒤로 이동한다.
            setTimeout(function() {
                hamster.wheels(30, -30); // 오른쪽으로 회전한다.
                setTimeout(function() {
                    sensing = true;
                }, Math.random() * 1000);
            }, 500);
        } else if(hamster.rightFloor() < 20) { // 오른쪽 바닥 센서가 검은색 선 위에 있으면
            sensing = false;
            hamster.wheels(-30); // 0.5초 뒤로 이동한다.
            setTimeout(function() {
                hamster.wheels(-30, 30); // 왼쪽으로 회전한다.
                setTimeout(function() {
                    sensing = true;
                }, Math.random() * 1000);
            }, 500);
        } else {
            hamster.wheels(30);
        }
    }
}

검은색 선을 감지하는 판단 기준인 숫자 20과 회전하는 시간의 범위를 변경하면서 로봇의 이동 형태를 좀 더 발전시켜 봅시다.
벽과 가장자리 피하기
햄스터 로봇이 벽에 부딪치는 것도 피하고, 마루 아래로 떨어지는 것도 방지하면서 청소하도록 해봅시다. 실험을 위해 아래 그림과 같이 세 면은 책으로 막고 나머지 한 면은 검은색 테이프로 경계를 만들어 거실을 꾸미도록 합시다.

var hamster = Hamster.create();
var sensing = true;
var left = true;

function execute() {
    // 청소하고 있음을 LED로 표시한다.
    if(left) {
        hamster.leds(Hamster.LED_BLUE, 0);
    } else {
        hamster.leds(0, Hamster.LED_BLUE);
    }
    left = !left;

    if(sensing) {
        // 가장자리를 감지하면 회전한다.
        if(hamster.leftFloor() < 20) {
            sensing = false;
            hamster.wheels(-30);
            setTimeout(function() {
                hamster.wheels(30, -30);
                setTimeout(function() {
                    sensing = true;
                }, Math.random() * 1000);
            }, 500);
        } else if(hamster.rightFloor() < 20) {
            sensing = false;
            hamster.wheels(-30);
            setTimeout(function() {
                hamster.wheels(-30, 30);
                setTimeout(function() {
                    sensing = true;
                }, Math.random() * 1000);
            }, 500);
        }

        // 벽을 감지하면 회전한다.
        else if(hamster.leftProximity() > 50) {
            sensing = false;
            hamster.wheels(30, -30);
            setTimeout(function() {
                sensing = true;
            }, Math.random() * 1000);
        } else if(hamster.rightProximity() > 50) {
            sensing = false;
            hamster.wheels(-30, 30);
            setTimeout(function() {
                sensing = true;
            }, Math.random() * 1000);
        }

        // 앞으로 이동한다.
        else {
            hamster.wheels(30);
        }
    }
}

보통 위와 같이 코드를 작성하는 경우가 많습니다. 즉, 벽을 감지하는 조건과 가장자리를 감지하는 조건을 설정하고 각 조건에 따라 다른 동작을 하도록 만드는 것입니다. 하지만 이러한 코드는 아주 비효율적이고 버그가 발생하기 쉽습니다. 주석을 표시하여 영역을 구분하기는 했지만 여전히 복잡하고 전체 논리(Logic)가 한 눈에 들어오지 않습니다.
또 다른 조건과 동작이 필요하면 어떻게 할까요? 조건이 추가될 때마다 전체 코드는 급격하게 복잡해질 것입니다. 사람이 한 번에 생각할 수 있는 생각의 범위는 모니터 화면에 표시되는 분량 만큼입니다. 논리 구조를 표현한 코드가 모니터 화면을 넘어가면 한 번에 생각하기가 어렵습니다.
생각을 나누어 표현하기
로봇의 행동을 단위 행동으로 나누어 생각하는 것이 편합니다. 구현하고자 하는 로봇의 행동은 다음과 같이 여섯 개의 단위 행동으로 나누면 문제가 쉬워집니다.

각각의 단위 행동을 함수로 만들어 봅시다.

var hamster = Hamster.create();
var sensing = true;
var left = true;

// 청소하고 있음을 표시한다.
function cleaning() {
    if(left) {
        hamster.leds(Hamster.LED_BLUE, 0);
    } else {
        hamster.leds(0, Hamster.LED_BLUE);
    }
    left = !left;
}

// 가장자리를 피하기 위해 왼쪽으로 회전한다.
function turnLeftEdge() {
    sensing = false;
    hamster.wheels(-30);
    setTimeout(function() {
        hamster.wheels(30, -30);
        setTimeout(function() {
            sensing = true;
        }, Math.random() * 1000);
    }, 500);
}

// 가장자리를 피하기 위해 오른쪽으로 회전한다.
function turnRightEdge() {
    sensing = false;
    hamster.wheels(-30);
    setTimeout(function() {
        hamster.wheels(-30, 30);
        setTimeout(function() {
            sensing = true;
        }, Math.random() * 1000);
    }, 500);
}

// 벽과 부딪치지 않도록 왼쪽으로 회전한다.
function turnLeftWall() {
    sensing = false;
    hamster.wheels(30, -30);
    setTimeout(function() {
        sensing = true;
    }, Math.random() * 1000);
}

// 벽과 부딪치지 않도록 오른쪽으로 회전한다.
function turnRightWall() {
    sensing = false;
    hamster.wheels(-30, 30);
    setTimeout(function() {
        sensing = true;
    }, Math.random() * 1000);
}

// 앞으로 이동한다.
function moveForward() {
    hamster.wheels(30);
}

function execute() {
    cleaning();

    if(sensing) {
        if(hamster.leftFloor() < 20) {
            turnRightEdge();
        } else if(hamster.rightFloor() < 20) {
            turnLeftEdge();
        } else if(hamster.leftProximity() > 50) {
            turnRightWall();
        } else if(hamster.rightProximity() > 50) {
            turnLeftWall();
        } else {
            moveForward();
        }
    }
}

이와 같이 각 단위 행동을 함수로 만드는 것의 장점은 같은 내용의 코드를 재활용할 수 있다는 점도 있지만, 더욱 중요한 것은 각 함수 단위로 나누어 생각할 수 있다는 것입니다.

따라서 함수를 구성할 때 주의해야 하는 점은 다음과 같습니다.

좀 더 세련되게 만들기
앞서 작성한 코드의 execute() 함수에 있는 조건문은 좀 더 정리할 필요가 있습니다. 아주 많은 조건이 추가되면 조건문 전체가 길어져서 모니터 화면을 넘어가게 된다는 문제도 있지만, 그보다 더 큰 문제는 서로 다른 성격의 조건식이 섞여 있다는 것입니다.

벽에 대한 조건과 가장자리에 대한 조건을 나누어 코드를 작성하면 다음과 같이 정리할 수 있습니다.

var hamster = Hamster.create();
var sensing = true;
var left = true;

// 청소하고 있음을 표시한다.
function cleaning() {
    if(left) {
        hamster.leds(Hamster.LED_BLUE, 0);
    } else {
        hamster.leds(0, Hamster.LED_BLUE);
    }
    left = !left;
}

// 가장자리를 피하기 위해 왼쪽으로 회전한다.
function turnLeftEdge() {
    sensing = false;
    hamster.wheels(-30);
    setTimeout(function() {
        hamster.wheels(30, -30);
        setTimeout(function() {
            sensing = true;
        }, Math.random() * 1000);
    }, 500);
}

// 가장자리를 피하기 위해 오른쪽으로 회전한다.
function turnRightEdge() {
    sensing = false;
    hamster.wheels(-30);
    setTimeout(function() {
        hamster.wheels(-30, 30);
        setTimeout(function() {
            sensing = true;
        }, Math.random() * 1000);
    }, 500);
}

// 벽과 부딪치지 않도록 왼쪽으로 회전한다.
function turnLeftWall() {
    sensing = false;
    hamster.wheels(30, -30);
    setTimeout(function() {
        sensing = true;
    }, Math.random() * 1000);
}

// 벽과 부딪치지 않도록 오른쪽으로 회전한다.
function turnRightWall() {
    sensing = false;
    hamster.wheels(-30, 30);
    setTimeout(function() {
        sensing = true;
    }, Math.random() * 1000);
}

// 앞으로 이동한다.
function moveForward() {
    hamster.wheels(30);
}

// 벽을 감지하면 회전한다.
function handleWall() {
    if(hamster.leftProximity() > 50) {
        turnRightWall();
    } else if(hamster.rightProximity() > 50) {
        turnLeftWall();
    } else {
        moveForward();
    }
}

// 가장자리를 감지하면 회전한다.
function handleEdge() {
    if(hamster.leftFloor() < 20) {
        turnRightEdge();
    } else if(hamster.rightFloor() < 20) {
        turnLeftEdge();
    } else {
        handleWall();
    }
}

function execute() {
    cleaning();

    if(sensing) {
        handleEdge();
    }
}

우선 앞으로 이동하는 행동에 벽을 감지하는 조건을 추가하여 handleWall()을 만들었습니다. 여기에 다시 가장자리를 감지하는 조건을 추가하여 handleEdge()를 만들었습니다. 즉, 앞서 작성한 코드가 조건문을 전부 나열한 평면 구조라면 수정된 코드는 조건이 하나씩 추가되는 계층 구조로 되어 있습니다. 앞서 작성한 코드와 비교해 보면 훨씬 간단하고 명확합니다. 여러 가지 계층 구조 중에서 이러한 구조를 특별히 포섭 구조라고 하는데 이에 대해서는 추후 자세히 다룰 것입니다. (행위 기반의 로봇 제어)

조건을 하나 더 추가해 볼까요? 어두워지면 삐 소리를 내도록 해봅시다.

var hamster = Hamster.create();
var sensing = true;
var left = true;

// 청소하고 있음을 표시한다.
function cleaning() {
    if(left) {
        hamster.leds(Hamster.LED_BLUE, 0);
    } else {
        hamster.leds(0, Hamster.LED_BLUE);
    }
    left = !left;
}

// 가장자리를 피하기 위해 왼쪽으로 회전한다.
function turnLeftEdge() {
    sensing = false;
    hamster.wheels(-30);
    setTimeout(function() {
        hamster.wheels(30, -30);
        setTimeout(function() {
            sensing = true;
        }, Math.random() * 1000);
    }, 500);
}

// 가장자리를 피하기 위해 오른쪽으로 회전한다.
function turnRightEdge() {
    sensing = false;
    hamster.wheels(-30);
    setTimeout(function() {
        hamster.wheels(-30, 30);
        setTimeout(function() {
            sensing = true;
        }, Math.random() * 1000);
    }, 500);
}

// 벽과 부딪치지 않도록 왼쪽으로 회전한다.
function turnLeftWall() {
    sensing = false;
    hamster.wheels(30, -30);
    setTimeout(function() {
        sensing = true;
    }, Math.random() * 1000);
}

// 벽과 부딪치지 않도록 오른쪽으로 회전한다.
function turnRightWall() {
    sensing = false;
    hamster.wheels(-30, 30);
    setTimeout(function() {
        sensing = true;
    }, Math.random() * 1000);
}

// 앞으로 이동한다.
function moveForward() {
    hamster.wheels(30);
}

// 벽을 감지하면 회전한다.
function handleWall() {
    if(hamster.leftProximity() > 50) {
        turnRightWall();
    } else if(hamster.rightProximity() > 50) {
        turnLeftWall();
    } else {
        moveForward();
    }
}

// 가장자리를 감지하면 회전한다.
function handleEdge() {
    if(hamster.leftFloor() < 20) {
        turnRightEdge();
    } else if(hamster.rightFloor() < 20) {
        turnLeftEdge();
    } else {
        handleWall();
    }
}

// 어두우면 삐 소리를 낸다.
function handleLight() {
    if(hamster.light() < 100) {
        hamster.buzzer(1000);
    } else {
        hamster.buzzer(0);
        handleEdge();
    }
}

function execute() {
    cleaning();

    if(sensing) {
        handleLight();
    }
}

코드를 멋있게 작성한 것 같지만 아직 한 가지 문제가 더 남아 있습니다. setTimeout() 함수에 의해 로봇이 움직이고 있는 동안에는 센서 값을 얻을 수 없다는 것입니다. 이 때문에 로봇에 적합한 코드는 아닙니다. 로봇을 동작시키기 위한 코드에서는 항상 센서 값을 얻을 수 있어야 합니다. 위험 상황에서 즉시 정지해야 하니까요.

이를 다루기 위한 코드의 구조는 조금 복잡하기 때문에 추후 "행위 기반의 로봇 제어"에서 다루도록 하겠습니다.
목차
수업 자료 내려 받기
수업 준비
  1. 하드웨어 살펴보기
  2. 햄스터 · 햄스터S · USB 동글 PDF · PPT
  3. 소프트웨어 설치
  4. PDF · PPT
  5. 로봇과 컴퓨터 연결
  6. PDF · PPT
  7. 소프트웨어 실행
  8. PDF · PPT
기초
  1. 코드의 기본 형태 만들기
  2. 이동하고 회전하기
  3. LED 켜고 소리 내기
  4. 순서대로, 반복하여 명령하기
  5. 키보드 이벤트
  6. 근접 센서 사용하기
  7. 바닥 센서 사용하기
  8. 밝기 센서와 가속도 센서 사용하기
  9. 브레이튼버그의 로봇
심화
  1. 말판 이동하기
  2. 보드 게임 만들기
  3. 그래픽 인터페이스
  4. 센서 한 개를 사용한 라인 트레이서
  5. 센서 두 개를 사용한 라인 트레이서
  6. 햄스터 친구 따라가기 (2인 1조)
  7. 벽 따라가기
  8. 로봇 청소기 흉내 내기
  9. 라인 트레이서 교차로 주행하기
  10. 미로 탈출
확장 키트
  1. 조립하기
  2. 핀/소켓 배치 살펴보기
  3. 디지털 입력 - 버튼을 누르면 삐 소리가 나요
  4. 디지털 출력 - 어두우면 LED 불이 켜져요
  5. 디지털 출력 - 반짝반짝 LED를 깜박여요
  6. 디지털 출력 - 기울이는 방향으로 LED가 켜져요
  7. 아날로그 입력 - 포텐셔미터를 돌리면 음 높이가 달라져요
  8. 아날로그 입력 - 뜨겁지 않게 해주세요
  9. 아날로그 입력 - 빛을 따라 움직여요
  10. PWM 출력 - LED 불이 부드럽게 밝아졌다 어두워져요
  11. PWM 출력 - LED 촛불이 바람에 흔들려요
  12. 아날로그 서보 출력 - 햄스터 로봇에게 꼬리가 생겼어요
고급
  1. 행위 기반의 로봇 제어
  2. 경로 탐색
  3. 자리 바꾸기
Copyright 로봇SW교육원 All rights reserved.
어려운 일이 있으면 광운대학교 로봇학부 박광현 교수(akaii@kw.ac.kr)에게 연락하세요.