로보테크AI

융합_로보테크 AI 자율주행 로봇 개발자 과정-26/01/29

steezer 2026. 1. 29. 18:30
(\theta) 배율 (1/\cos\theta) (d_0=70)일 때 기대 거리
1.000 70.0
10° 1.015 71.1
20° 1.064 74.5
30° 1.155 80.8
40° 1.305 91.4
50° 1.556 108.9
60° 2.000 140.0
70° 2.924 204.7
80° 5.759 403.1

 

직진 멈춤 해결

 

벽 따라돌기(정면 벽 너무 빨리 감지해서 회전 자주 함)

좁은 통로 따라가기

import requests
import threading
import time


class RobotWallFollower:
    def __init__(self):
        # 네트워크 설정
        self.base_url = "http://192.168.0.244:5000/control"

        # 주행 설정
        self.TARGET_DIST = 70  # 벽과의 목표 유지 거리
        self.MARGIN = 5  # 허용 오차 (65~75cm 사이면 직진)
        self.BASE_SPEED = 0.12  # 기본 전진 속도
        self.ALIGN_THRESHOLD = 3  # 평행 판단 임계값 (작을수록 예민함)

        # 상태 관리
        self.sensor_data = []
        self.lock = threading.Lock()
        self.running = True

        # 센서 읽기 스레드 시작
        self.thread = threading.Thread(target=self._update_sensor_loop, daemon=True)
        self.thread.start()

    def _update_sensor_loop(self):

        while self.running:
            try:
                # 파라미터 없이 호출하여 데이터 수신
                response = requests.get(self.base_url, timeout=0.5)
                if response.status_code == 200:
                    data = response.json()
                    with self.lock:
                        self.sensor_data = data.get('s', [])
            except Exception:
                pass
            time.sleep(0.1)  # 10Hz 업데이트

    def send_control(self, lin, ang):

        # f-string을 사용하여 ?lin=0.15&ang=0.0 형태의 URL 생성
        url = f"{self.base_url}?lin={lin}&ang={ang}"
        try:
            requests.get(url, timeout=0.3)
        except Exception as e:
            print(f"⚠️ 전송 오류: {e}")

    def run(self):
        print(f"🚀 로봇 벽 추종 시스템 가동 (목표 거리: {self.TARGET_DIST})")

        try:
            while self.running:
                with self.lock:
                    if len(self.sensor_data) < 36:
                        continue
                    lidar = self.sensor_data

                # 1. 방향별 데이터 추출 (360도 스캔 가정)
                # 정면 (0도 부근)
                front_dist = min(lidar[0:2] + lidar[34:36])

                # 왼쪽 벽 (약 70~110도 구간)
                l_front, l_back = lidar[7], lidar[11]

                # 오른쪽 벽 (약 250~290도 구간)
                r_front, r_back = lidar[29], lidar[25]

                # 기본 속도 설정
                lin_speed = self.BASE_SPEED
                ang_speed = 0.0

                # 2. 전방 장애물 판단
                if front_dist < 50:
                    print(f"🛑 전방 위험! 회전 피동 ({front_dist})")
                    self.send_control(0.0, 0.5)  # 제자리 회전
                    time.sleep(0.5)
                    continue

                # 3. 벽 추종 로직 (더 가까운 벽 기준)
                left_min = min(l_front, l_back)
                right_min = min(r_front, r_back)

                if left_min < right_min:
                    # --- 왼쪽 벽 따라가기 ---
                    diff = l_front - l_back  # 양수면 머리가 벽에서 멀어지는 중
                    avg_dist = (l_front + l_back) / 2

                    if abs(diff) > self.ALIGN_THRESHOLD:
                        # 평행이 깨졌으면 각도부터 교정 (일직선 맞추기)
                        ang_speed = -0.15 if diff > 0 else 0.15
                    elif avg_dist > self.TARGET_DIST + self.MARGIN:
                        # 벽에서 멀어지면 왼쪽으로 슬쩍 붙기
                        ang_speed = -0.1
                    elif avg_dist < self.TARGET_DIST - self.MARGIN:
                        # 벽에 너무 붙으면 오른쪽으로 떨어지기
                        ang_speed = 0.1
                else:
                    # --- 오른쪽 벽 따라가기 ---
                    diff = r_front - r_back
                    avg_dist = (r_front + r_back) / 2

                    if abs(diff) > self.ALIGN_THRESHOLD:
                        # 평행 교정
                        ang_speed = 0.15 if diff > 0 else -0.15
                    elif avg_dist > self.TARGET_DIST + self.MARGIN:
                        # 오른쪽으로 붙기
                        ang_speed = 0.1
                    elif avg_dist < self.TARGET_DIST - self.MARGIN:
                        # 왼쪽으로 떨어지기
                        ang_speed = -0.1

                # 명령 전송
                self.send_control(lin_speed, ang_speed)
                time.sleep(0.1)

        except KeyboardInterrupt:
            self.stop()

    def stop(self):
        self.running = False
        self.send_control(0.0, 0.0)
        print("\n🛑 시스템이 안전하게 종료되었습니다.")


if __name__ == "__main__":
    bot = RobotWallFollower()
    bot.run()

 

센서 36 -> 180개로 변경

 

앞에 있는 a, x, y는 a(앵글) x, y(라디안)는 가로 위치 세로 위치

"p":{"a":0.118,"x":0.626,"y":-0.008}
{"p":{"a":1.811,"x":29.164,"y":30.925},"s":[25,25,25,26,26,26,26,0,26,26,27,28,0,118,113,104,101,94,92,88,84,82,77,76,74,71,70,67,66,65,63,63,61,61,59,59,58,58,57,57,56,56,56,56,56,56,56,56,56,57,57,58,59,59,60,0,62,62,63,65,66,68,69,72,73,75,0,79,83,85,86,0,91,96,98,104,107,111,119,123,134,140,147,146,144,142,141,140,139,0,139,138,139,139,139,140,140,141,142,143,146,144,127,121,114,104,99,92,88,82,80,77,68,64,61,60,58,56,55,54,53,51,51,50,49,49,48,48,47,47,47,46,46,46,46,46,46,46,46,47,47,47,47,48,49,49,50,50,51,52,54,55,55,57,58,61,63,0,67,68,73,75,77,83,86,93,97,106,112,119,126,135,139,137,136,135,134,0,26,25]}

 

봇 들어서 이동시킬 경우 

오도메트리 기반이라 실제 위치와 P 위치가 일치하지 않게 됨

 

오도메트리(Odometry, 주행기록계)는 휠 엔코더, IMU(관성 측정 장비), 카메라, {Link: LiDAR 등 자체 센서 데이터를 활용해 이전 위치를 기준으로 현재 로봇의 상대적 위치와 자세 변화량을 추정하는 기술입니다. 고속 샘플링이 가능해 실시간 제어에 유용하나, 이동 거리가 길어질수록 누적 오차(Drift)가 발생하는 특징

 

import requests
import threading
import time
import math

class FinalRobotFollower:
    def __init__(self):
        self.base_url = "http://192.168.0.244:5000/control"
        self.LIDAR_N = 180

        # ---- 주행 설정 ----
        self.TARGET_DIST = 70.0
        self.BASE_SPEED = 0.12
        self.SLOW_SPEED = 0.06          # 목표 근처 감속
        self.SLOW_RADIUS = 0.25         # (m) 이 안이면 감속
        self.MAX_ANG_SPEED = 0.35

        # ---- 제어 및 필터 ----
        self.KP_ANGLE = 0.03
        self.KP_DIST = 0.018
        self.EMA_ALPHA = 0.3
        self.ANG_SLEW = 0.05
        self.DEAD_DIST = 2.0
        self.DEAD_DIFF = 1.5

        # ---- 센서 임계값 ----
        self.FRONT_BLOCK = 45.0
        self.WALL_LOST = 140.0

        # ---- 도착 판정 ----
        self.POSE_IS_METER = True
        f_cm, l_cm = 12.0, 35.0
        self.FINISH_TOL = (f_cm / 100.0) if self.POSE_IS_METER else f_cm
        self.LEAVE_TOL  = (l_cm / 100.0) if self.POSE_IS_METER else l_cm
        self.HOME_HOLD = 5
        self.home_count = 0

        # ---- 상태 ----
        self.sensor_data = []
        self.pose = {"x": 0.0, "y": 0.0, "a": 0.0}
        self.start_pose = None
        self.has_left_home = False

        self.prev_ang = 0.0
        self.smooth_r_avg = self.TARGET_DIST
        self.smooth_r_diff = 0.0

        self.lock = threading.Lock()
        self.running = True
        threading.Thread(target=self._update_data_loop, daemon=True).start()

    def _update_data_loop(self):
        while self.running:
            try:
                r = requests.get(self.base_url, timeout=0.5)
                if r.status_code == 200:
                    data = r.json()
                    p, s = data.get("p"), data.get("s")
                    if isinstance(s, list) and len(s) >= self.LIDAR_N and isinstance(p, dict):
                        with self.lock:
                            self.sensor_data = s[:self.LIDAR_N]
                            self.pose["x"] = float(p.get("x", self.pose["x"]))
                            self.pose["y"] = float(p.get("y", self.pose["y"]))
                            self.pose["a"] = float(p.get("a", self.pose["a"]))
                            if self.start_pose is None:
                                self.start_pose = (self.pose["x"], self.pose["y"])
                                print(f"[START] {self.start_pose}", flush=True)
            except Exception:
                pass
            time.sleep(0.05)

    def send_control(self, lin, ang):
        try:
            requests.get(f"{self.base_url}?lin={lin}&ang={ang}", timeout=0.2)
        except Exception:
            pass

    @staticmethod
    def _nz(v, default=999.0):
        return v if (isinstance(v, (int, float)) and v > 0) else default

    def run(self):
        print("[RUN] final follower", flush=True)
        try:
            while self.running:
                with self.lock:
                    if not self.sensor_data or self.start_pose is None:
                        time.sleep(0.02)
                        continue
                    lidar = self.sensor_data[:]
                    curr_x, curr_y = self.pose["x"], self.pose["y"]
                    sx, sy = self.start_pose

                dist_home = math.hypot(curr_x - sx, curr_y - sy)

                # 출발/도착 판정
                if (not self.has_left_home) and dist_home > self.LEAVE_TOL:
                    self.has_left_home = True
                    print("[LEAVE]", flush=True)

                if self.has_left_home and dist_home <= self.FINISH_TOL:
                    self.home_count += 1
                    if self.home_count >= self.HOME_HOLD:
                        print(f"[ARRIVE] {dist_home:.3f}m -> STOP", flush=True)
                        self.stop()
                        break
                else:
                    self.home_count = 0

                # 목표 근처 감속
                base_v = self.SLOW_SPEED if (self.has_left_home and dist_home < self.SLOW_RADIUS) else self.BASE_SPEED

                # 센서
                front = min([v for v in (lidar[0:15] + lidar[165:180]) if v > 0] or [999.0])

                r_front = self._nz(lidar[120])
                r_back  = self._nz(lidar[150])
                r_avg = (r_front + r_back) / 2.0
                r_diff = r_front - r_back

                # EMA
                self.smooth_r_avg = (r_avg * self.EMA_ALPHA) + (self.smooth_r_avg * (1 - self.EMA_ALPHA))
                self.smooth_r_diff = (r_diff * self.EMA_ALPHA) + (self.smooth_r_diff * (1 - self.EMA_ALPHA))

                # 제어
                lin, ang_target = base_v, 0.0
                mode = "WALL"

                if front < self.FRONT_BLOCK:
                    mode, lin, ang_target = "LEFT", 0.02, -0.40
                elif self.smooth_r_avg > self.WALL_LOST:
                    mode, lin, ang_target = "SEARCH", 0.08, 0.30
                else:
                    diff_err = 0.0 if abs(self.smooth_r_diff) < self.DEAD_DIFF else self.smooth_r_diff
                    dist_err = self.smooth_r_avg - self.TARGET_DIST
                    dist_err = 0.0 if abs(dist_err) < self.DEAD_DIST else dist_err
                    ang_target = (diff_err * self.KP_ANGLE) + (dist_err * self.KP_DIST)

                # Slew + clamp
                delta = max(min(ang_target - self.prev_ang, self.ANG_SLEW), -self.ANG_SLEW)
                ang = self.prev_ang + delta
                ang = max(min(ang, self.MAX_ANG_SPEED), -self.MAX_ANG_SPEED)
                self.prev_ang = ang

                self.send_control(lin, ang)

                if int(time.time() * 10) % 5 == 0:
                    print(f"[{mode}] home={dist_home:.3f} hold={self.home_count}/{self.HOME_HOLD} "
                          f"F={front:5.1f} R={self.smooth_r_avg:5.1f} cmd=({lin:.2f},{ang:.2f})",
                          flush=True)
                time.sleep(0.1)

        except KeyboardInterrupt:
            self.stop()

    def stop(self):
        self.running = False
        self.send_control(0.0, 0.0)
        print("[STOP]", flush=True)

if __name__ == "__main__":
    FinalRobotFollower().run()

 

더보기
더보기

# RobotWallFollower  
우측 벽 추종 + 원점(출발점) 복귀 주행 알고리즘

---

## 1. 개요

이 프로그램은 **라이다 기반 우측 벽 추종 알고리즘**을 사용하여 로봇을 주행시키고,  
**출발 지점을 기준으로 한 바퀴를 돈 뒤 다시 출발점 근처로 복귀하면 정지**하도록 설계되어 있다.

핵심 목표는 다음과 같다.

- 우측 벽을 일정 거리로 안정적으로 따라간다
- 전방 충돌을 최우선으로 회피한다
- 출발 지점을 기준으로 “한 바퀴 주행”을 판단한다
- 출발점 근처에 도달하면 감속 후 정확히 정지한다
- 불필요한 떨림(좌우 잔진동)을 최소화한다

---

## 2. 입력 / 출력 및 단위

### 입력 (서버 `/control` 응답 JSON)
- `p.x`, `p.y`, `p.a` : 로봇 위치 및 방향  
  - **단위: meter (m)**
- `s` : 라이다 배열 (길이 180)  
  - **단위: centimeter (cm)**

### 출력 (서버 `/control` 요청)
- `lin` : 선속도
- `ang` : 각속도

> ⚠️ 주의  
> - **포즈(p)는 meter**, **라이다(s)는 centimeter** 기준으로 사용한다.  
> - 도착/출발 판단 거리(cm)는 내부에서 meter로 변환되어 비교된다.

---

## 3. 전체 구조

### 스레드 구성
1. **데이터 수집 스레드**
   - 주기: 0.05초
   - 역할:
     - 센서(`s`)와 포즈(`p`) 수신
     - 최초 1회 현재 위치를 `start_pose`로 저장
     - 센서 길이가 충분할 때만 데이터 갱신

2. **제어 루프(run)**
   - 주기: 0.1초
   - 역할:
     - 센서/포즈 스냅샷 사용
     - 원점 복귀 판단
     - 주행 모드 결정
     - 속도 명령 전송

---

## 4. 주요 상태 변수

| 변수 | 설명 |
|----|----|
| `start_pose` | 출발점 좌표 (최초 1회 저장) |
| `has_left_home` | 출발점에서 충분히 멀어졌는지 여부 |
| `home_count` | 도착 조건 연속 만족 횟수 |
| `prev_ang` | 이전 루프의 각속도 (떨림 억제용) |
| `sensor_data` | 최신 라이다 데이터 |
| `pose` | 최신 위치/각도 |

---

## 5. 핵심 파라미터 설명

### 벽 추종 관련
- `TARGET_DIST (cm)`  
  우측 벽과 유지하려는 목표 거리
- `MARGIN (cm)`  
  목표 거리 허용 오차 (데드존)
- `ALIGN_THRESHOLD (cm)`  
  우측 전방/후방 센서 차이가 이 값보다 크면 “기울어짐”으로 판단

### 충돌 / 탐색
- `FRONT_BLOCK (cm)`  
  정면 장애물 판단 기준
- `WALL_LOST (cm)`  
  우측 벽을 잃었다고 판단하는 거리

### 복귀 / 정지
- `LEAVE_THRESHOLD (cm)`  
  출발점에서 이 거리 이상 멀어져야 “주행 시작”으로 인정
- `FINISH_TOLERANCE (cm)`  
  출발점으로부터 이 거리 이내면 “도착 후보”
- `HOME_HOLD`  
  도착 조건을 연속 몇 번 만족해야 정지할지
- `SLOW_RADIUS (cm)`  
  출발점 근처 감속 반경

### 안정화
- `ANG_SLEW`  
  1 루프(0.1s)당 각속도 변화 제한값  
  → 떨림 억제의 핵심

---

## 6. 센서 해석 방식

### 정면 거리
- 사용 인덱스: `0~10`, `170~180`
- 처리:
  - 0 이하 값 제거
  - 유효 값 중 **최소값** 사용

### 우측 벽 센서
- `r_front`, `r_back` (우측 전방 / 후방)
- 계산값:
  - `avg = (r_front + r_back) / 2`
  - `diff = r_front - r_back`

| 값 | 의미 |
|---|---|
| `avg` | 벽까지 평균 거리 |
| `diff` | 로봇이 벽과 평행한지 여부 |

---

## 7. 주행 판단 우선순위 (가장 중요)

아래 순서가 **절대적인 우선순위**다.

### 1️⃣ 전방 충돌 회피 (최우선)
조건:
- `front < FRONT_BLOCK`

동작:
- `lin = 0`
- `ang = 좌회전`
- 짧게 회전 후 다시 판단

---

### 2️⃣ 우측 벽 분실 (탐색)
조건:
- `avg > WALL_LOST` 또는 센서 무효

동작:
- 전진 + 우회전
- 우측에 벽이 다시 잡힐 때까지 유지

---

### 3️⃣ 우측 벽 추종 (기본 상태)

#### 3-1. 정렬 (평행 맞추기)
조건:
- `abs(diff) > ALIGN_THRESHOLD`

동작:
- 고정 각속도로 좌/우 회전

#### 3-2. 거리 유지
- 벽이 멀면 → 접근
- 벽이 가까우면 → 이탈
- 적정 거리면 → 직진

> 이 구조 덕분에 **11자 형태의 안정적인 벽 추종**이 유지된다.

---

## 8. 각속도 안정화 (Slew Rate Limit)

각 루프에서 계산된 `ang_target`을 바로 쓰지 않고,
이전 각속도(`prev_ang`)와의 차이를 제한한다.

delta = clamp(ang_target - prev_ang, -ANG_SLEW, +ANG_SLEW)
ang = prev_ang + delta

효과:
- 좌/우 명령 급변 방지
- 삐뚤삐뚤한 주행 감소
- 로봇 궤적이 부드러워짐

---

## 9. 원점 복귀 및 정지 로직

1. 출발 직후에는 도착 판정을 하지 않음
2. `LEAVE_THRESHOLD`를 넘어가면 `has_left_home = True`
3. 이후 `FINISH_TOLERANCE` 이내 진입 시 `home_count++`
4. `home_count >= HOME_HOLD`가 되면 정지

### 감속 전략
- 출발점 근처(`SLOW_RADIUS`)에서는 선속도를 낮춰
  - 반경을 지나쳐버리는 문제 방지
  - 도착 판정 안정성 확보

---

## 10. 튜닝 가이드

### 벽에 너무 붙을 때
- `TARGET_DIST` ↑
- `MARGIN` ↑
- 이탈 각속도 ↑

### 좌우로 흔들릴 때
- `ALIGN_THRESHOLD` ↑
- `ANG_SLEW` ↓
- 정렬/접근/이탈 각속도 ↓

### 도착을 자꾸 놓칠 때
- `FINISH_TOLERANCE` ↑
- `HOME_HOLD` ↓
- `SLOW_RADIUS` ↑

---

## 11. 설계 철학 요약

- **벽 추종보다 충돌 회피가 항상 우선**
- **연속 조건(HOLD)으로 오판 제거**
- **단순한 계단형 제어로 예측 가능성 확보**
- **필터보다 Slew 제한을 중심으로 안정화**
- **디버깅과 튜닝이 쉬운 구조**

---

import requests
import threading
import time
import math


class RobotWallFollower:
    def __init__(self):
        self.base_url = "http://192.168.0.244:5000/control"
        self.LIDAR_N = 180

        # ----------------------------
        # (1) 단위/도착 판정 (pose=m, lidar=cm)
        # ----------------------------
        self.POSE_IS_METER = True

        finish_cm = 10.0      # ✅ 요청: 도착 범위 10cm
        leave_cm = 40.0       # 출발 판정
        slow_cm = 25.0        # 근처 감속 반경
        self.FINISH_TOL_M = (finish_cm / 100.0) if self.POSE_IS_METER else finish_cm
        self.LEAVE_TOL_M = (leave_cm / 100.0) if self.POSE_IS_METER else leave_cm
        self.SLOW_RADIUS_M = (slow_cm / 100.0) if self.POSE_IS_METER else slow_cm

        self.HOME_HOLD = 8    # ✅ 연속 N회 들어오면 정지(스쳐 지나감 방지)
        self.home_count = 0

        # ----------------------------
        # (2) 주행 파라미터 (예전 스타일 유지)
        # ----------------------------
        self.TARGET_DIST = 65.0      # cm
        self.MARGIN = 3.0            # cm
        self.BASE_SPEED = 0.12
        self.SLOW_SPEED = 0.06
        self.ALIGN_THRESHOLD = 2.5   # cm(앞/뒤 차이)

        # 고정 조향값(계단형) - 예전 느낌 유지
        self.ANG_ALIGN = 0.15
        self.ANG_APPROACH = 0.10
        self.ANG_AWAY = -0.10

        # ----------------------------
        # (3) 센서 인덱스/임계값
        # ----------------------------
        # 네가 안정적이라 했던 조합을 기본으로 둠
        self.R_FRONT_IDX = 125
        self.R_BACK_IDX = 145
        # 만약 방향이 반대로 느껴지면 위 두 줄을 서로 바꿔서 테스트하면 됨.

        self.FRONT_BLOCK = 40.0   # cm
        self.WALL_LOST = 140.0    # cm (우측 벽이 멀어졌다 판단)

        # ----------------------------
        # (4) 떨림 최소화: 각속도 변화율 제한(슬루)만 "작게" 추가
        # ----------------------------
        self.ANG_SLEW = 0.06      # 0.1s 당 변화 제한
        self.prev_ang = 0.0

        # ----------------------------
        # 상태
        # ----------------------------
        self.sensor_data = []
        self.pose = {"x": 0.0, "y": 0.0, "a": 0.0}
        self.start_pose = None
        self.has_left_home = False

        self.lock = threading.Lock()
        self.running = True
        threading.Thread(target=self._update_data_loop, daemon=True).start()

    # ---------- I/O ----------
    def _update_data_loop(self):
        while self.running:
            try:
                r = requests.get(self.base_url, timeout=0.5)
                if r.status_code == 200:
                    data = r.json()
                    p = data.get("p", {})
                    s = data.get("s", [])
                    if isinstance(s, list) and len(s) >= self.LIDAR_N and isinstance(p, dict):
                        with self.lock:
                            self.sensor_data = s[: self.LIDAR_N]
                            self.pose["x"] = float(p.get("x", self.pose["x"]))
                            self.pose["y"] = float(p.get("y", self.pose["y"]))
                            self.pose["a"] = float(p.get("a", self.pose["a"]))
                            if self.start_pose is None:
                                self.start_pose = (self.pose["x"], self.pose["y"])
                                print(f"[START] start_pose={self.start_pose}", flush=True)
            except Exception:
                pass
            time.sleep(0.05)

    def send_control(self, lin, ang):
        try:
            requests.get(f"{self.base_url}?lin={lin}&ang={ang}", timeout=0.2)
        except Exception:
            pass

    def stop(self):
        self.running = False
        self.send_control(0.0, 0.0)
        print("[STOP]", flush=True)

    # ---------- helpers ----------
    @staticmethod
    def _min_pos(vals, default=999.0):
        m = default
        for v in vals:
            if isinstance(v, (int, float)) and v > 0 and v < m:
                m = v
        return m

    @staticmethod
    def _valid(v):
        return isinstance(v, (int, float)) and v > 0

    def _dist_home_m(self, x, y):
        sx, sy = self.start_pose
        return math.hypot(x - sx, y - sy)

    def _slew_ang(self, target_ang):
        diff = target_ang - self.prev_ang
        delta = max(min(diff, self.ANG_SLEW), -self.ANG_SLEW)
        ang = self.prev_ang + delta
        self.prev_ang = ang
        return ang

    # ---------- main ----------
    def run(self):
        print("[RUN] stable wall follower (old-style + arrive-hold + small-slew)", flush=True)

        try:
            while self.running:
                with self.lock:
                    if not self.sensor_data or self.start_pose is None:
                        time.sleep(0.02)
                        continue
                    lidar = self.sensor_data[:]
                    x, y = self.pose["x"], self.pose["y"]

                # ---- 출발/도착 판정 (m) ----
                dist_home = self._dist_home_m(x, y)

                if (not self.has_left_home) and dist_home > self.LEAVE_TOL_M:
                    self.has_left_home = True
                    print(f"[LEAVE] dist_home={dist_home:.3f}m", flush=True)

                if self.has_left_home and dist_home <= self.FINISH_TOL_M:
                    self.home_count += 1
                    if self.home_count >= self.HOME_HOLD:
                        print(f"[ARRIVE] dist_home={dist_home:.3f}m -> STOP", flush=True)
                        self.stop()
                        break
                else:
                    self.home_count = 0

                # ---- 속도: 목표 근처 감속 ----
                lin = self.SLOW_SPEED if (self.has_left_home and dist_home < self.SLOW_RADIUS_M) else self.BASE_SPEED

                # ---- 센서 ----
                front = self._min_pos(lidar[0:10] + lidar[170:180])
                r_front = lidar[self.R_FRONT_IDX]
                r_back = lidar[self.R_BACK_IDX]

                mode = "직진"
                ang_target = 0.0

                # ---- 1) 정면 회피 최우선 ----
                if 0 < front < self.FRONT_BLOCK:
                    mode = "회전"
                    lin = 0.0
                    ang_target = -0.40  # 좌회전(필요 시 부호 반전)
                    ang = self._slew_ang(ang_target)
                    self.send_control(lin, ang)
                    time.sleep(0.20)
                    continue

                # ---- 2) 우측 벽 기반 제어(예전 방식) ----
                if self._valid(r_front) and self._valid(r_back):
                    diff = r_front - r_back
                    avg = (r_front + r_back) / 2.0

                    # 벽이 너무 멀면(코너/벽 분실) 탐색: 우회전
                    if avg > self.WALL_LOST:
                        mode = "탐색"
                        ang_target = 0.25
                        lin = max(lin, 0.08)

                    # 정렬(평행)
                    elif abs(diff) > self.ALIGN_THRESHOLD:
                        mode = "정렬"
                        ang_target = self.ANG_ALIGN if diff > 0 else -self.ANG_ALIGN

                    # 거리 유지(접근/이탈)
                    elif avg > self.TARGET_DIST + self.MARGIN:
                        mode = "접근"
                        ang_target = self.ANG_APPROACH
                    elif avg < self.TARGET_DIST - self.MARGIN:
                        mode = "이탈"
                        ang_target = self.ANG_AWAY
                    else:
                        mode = "직진"
                        ang_target = 0.0
                else:
                    # 우측 값이 깨지면, 천천히 우회전하며 벽 찾기
                    mode = "탐색"
                    ang_target = 0.25
                    lin = max(lin, 0.08)

                # ---- 떨림 억제(슬루만 적용) ----
                ang = self._slew_ang(ang_target)
                self.send_control(lin, ang)

                # ---- 로그 ----
                if int(time.time() * 10) % 5 == 0:
                    print(
                        f"[{mode:^4}] home={dist_home:.3f}m hold={self.home_count}/{self.HOME_HOLD} "
                        f"F={front:5.1f} Rf={r_front:5.1f} Rb={r_back:5.1f} cmd=({lin:.2f},{ang:.2f})",
                        flush=True,
                    )

                time.sleep(0.1)

        except KeyboardInterrupt:
            self.stop()


if __name__ == "__main__":
    RobotWallFollower().run()

 

ROS-Flask

 

로봇 경로 탐색 통합 관제 UI(odom + lds02 센서 활용)

전체 화면은 tkinter로 구성

1. 구역 탐색(ROI)-캔버스 마우스 연동(클릭하면 해당 지점으로 이동해서 주변 구조 탐색(회전)하고 돌아오기)

2. 경로 표기(특정 포인트 tkinter에서 찍으면 거기로 이동, 총 이동 경로 표기/ 초기화 등)

3. 원격 복귀(특정 위치까지 갔다가 제자리로 복귀(전방, 후방 주차))

4. 웨이포인트 설정(중간 목적지 1, 2, 3을 찍으면 1, 2, 3에 들렸다가 최종목적지에 갔다가 복귀, 최대 5개까지 설정)

5. 시각화(라이다 센서) Matplot or Canvas widget

6. 로봇 이동 로그(비상정지 등)

7. DB 서버 mysql(orm model 구성, DB 연동)

8. GUI(전체를 패키징)

 

9. GUI 상 맵 사이즈는 x축 방향으로 200px, y축 방향으로 100px로 생성(실제 맵 200cm * 100cm)

10. 로봇의 출발 좌표는 맵 내에서 (50, 50) 지점으로 고정

 

~2.4까지

2.5 시연 발표

 

x,y 좌표 받아서 대각선(최적 경로)로 가서 360도 스캔(회전 크기 조정)한 수 복귀(스캔한 정보 json으로 DB에)

26029_C2.py
0.01MB