| (\theta) | 배율 (1/\cos\theta) | (d_0=70)일 때 기대 거리 |
| 0° | 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에)
'로보테크AI' 카테고리의 다른 글
| 융합_로보테크 AI 자율주행 로봇 개발자 과정-26/02/02 (0) | 2026.02.02 |
|---|---|
| 융합_로보테크 AI 자율주행 로봇 개발자 과정-26/01/30 (0) | 2026.01.30 |
| 융합_로보테크 AI 자율주행 로봇 개발자 과정-26/01/28 (1) | 2026.01.28 |
| 융합_로보테크 AI 자율주행 로봇 개발자 과정-26/01/27[TURTLEBOT] (1) | 2026.01.27 |
| 융합_로보테크 AI 자율주행 로봇 개발자 과정-26/01/26 (0) | 2026.01.26 |