Compare commits
3 Commits
3d8521ad89
...
feature/ta
| Author | SHA1 | Date | |
|---|---|---|---|
| 76ee9a52b6 | |||
| c61f73fa33 | |||
| 64c333e04d |
113
api_routes.py
113
api_routes.py
@@ -1,36 +1,48 @@
|
|||||||
"""팔레타이징 로봇 제어 API (책 재단기 라인).
|
"""팔레타이징 로봇 제어 API (책 재단기 라인).
|
||||||
|
|
||||||
[시스템 개요]
|
[시스템 개요]
|
||||||
재단기에서 나온 책 묶음이 컨베이어를 타고 '스태커(정렬대)'에 쌓이면,
|
재단기에서 나온 책이 낱권씩 컨베이어를 타고 '스태커(하부 적층 장치)'로 들어와
|
||||||
JAKA 로봇이 포크형 그리퍼로 묶음을 떠서 팔레트 위에 층층이 적재한다.
|
스택으로 쌓이고, 픽업량만큼 모이면 JAKA 로봇이 포크형 그리퍼로 스택을 통째로
|
||||||
|
떠서 팔레트 위에 층층이 적재한다.
|
||||||
이 파일은 Flask Blueprint 로서 웹 UI(webview) 요청을 받아 아래 전부를 수행한다.
|
이 파일은 Flask Blueprint 로서 웹 UI(webview) 요청을 받아 아래 전부를 수행한다.
|
||||||
- 팔레트 종류별 적재 메인 루프 (wooden_wheel / iron_wheel / wooden_Flat / one_line_*)
|
- 팔레트 종류별 적재 메인 루프 (wooden_wheel / iron_wheel / wooden_Flat / one_line_*)
|
||||||
- 로봇 상태 폴링 + 경광등/컨베이어 인터록 (get_status_thread)
|
- 로봇 상태 폴링 + 경광등/컨베이어 인터록 (get_status_thread)
|
||||||
- 스태커 실린더 시퀀스 제어 (stacker_thread, Modbus RTU)
|
- 스태커 실린더 시퀀스 제어 (stacker_thread, Modbus RTU)
|
||||||
|
|
||||||
[로봇 제어박스 I/O 맵 - IO_CABINET]
|
[로봇 제어박스 I/O 맵 - IO_CABINET]
|
||||||
DO0 : 그리퍼 수직 실린더(파지) 1=파지 / 0=해제
|
DO0 : 그리퍼 누름판(수직) 실린더 1=하강(파지) / 0=상승(해제)
|
||||||
|
(포크가 책 밑을 받친 상태에서 누름판이 위에서 내려와 눌러 고정)
|
||||||
DO1 : 그리퍼 수평 실린더(적재 푸셔) 1=전진 / 0=후퇴
|
DO1 : 그리퍼 수평 실린더(적재 푸셔) 1=전진 / 0=후퇴
|
||||||
DO2 : 경광등 주황 (마지막 층 근접 예고)
|
DO2 : 경광등 주황 (마지막 층 근접 예고)
|
||||||
DO3 : 경광등 초록 (정상 운전)
|
DO3 : 경광등 초록 (정상 운전)
|
||||||
DO4 : 경광등 빨강 (정지/완료/비상)
|
DO4 : 경광등 빨강 (정지/완료/비상)
|
||||||
DO5 : 동력장치(컨베이어) 운전 1=운전 / 0=정지
|
DO5 : 동력장치(컨베이어) 운전 1=운전 / 0=정지
|
||||||
DI0 : 스태커 수직 반사판 센서 (낱권 감지 → 스태커 1회 압축 동작 트리거)
|
DI0 : 스태커 책 인입 반사판 센서 (책이 들어오면 가려짐 → 리프트 사이클 트리거)
|
||||||
DI1 : 스태커 책 준비 센서 (묶음 완성 → 로봇 픽업 조건)
|
DI1 : 스태커 스택 준비 반사판 센서 (선반 위 스택이 픽업량만큼 쌓이면 가려짐 → 로봇 픽업)
|
||||||
DI2 : 스태커 수직 실린더 '위' 센서
|
DI2 : 스태커 리프터(수직 실린더) '위' 센서
|
||||||
DI3 : 스태커 수직 실린더 '아래' 센서 (완전히 내려가야 로봇 진입 가능)
|
DI3 : 스태커 리프터(수직 실린더) '아래' 센서
|
||||||
|
(완전 하강 상태여야 로봇 포크 진입 가능 + 컨베이어 재가동)
|
||||||
※ DI 번호는 get_robot_status() 반환값의 din 배열 인덱스 기준.
|
※ DI 번호는 get_robot_status() 반환값의 din 배열 인덱스 기준.
|
||||||
|
|
||||||
[Modbus RTU (COM3, slave 1) - 스태커 실린더]
|
[Modbus RTU (COM3, slave 1) - 스태커 실린더]
|
||||||
coil 0 : 스태커 수직 실린더 (책 누름)
|
coil 0 : 스태커 리프터(수직 실린더) 1=상승(책 밀어올림) / 0=하강
|
||||||
coil 1 : 스태커 수평 실린더 (책 밀어 정렬)
|
coil 1 : 스태커 선반(수평 실린더) 1=닫힘(스택 받침) / 0=열림(책 통과)
|
||||||
|
|
||||||
|
[스태커 동작 원리 - 하부 적층(bottom-stacking)]
|
||||||
|
1) 컨베이어로 들어온 책이 인입 센서(DI0)를 가림 → 리프터가 책을 위로 밀어올림.
|
||||||
|
리프터가 하강 위치를 벗어나 있는 동안(DI3=0) 컨베이어는 일시정지된다.
|
||||||
|
2) 리프트 중 선반(coil 1)이 잠시 열려, 책(과 그 위의 기존 스택)이 선반 높이를 통과
|
||||||
|
3) 선반이 다시 닫히고 리프터가 내려오면 새 책이 스택 맨 아래에 끼워진 채
|
||||||
|
스택 전체가 선반 위에 걸쳐진다
|
||||||
|
4) 스택이 픽업량(DI1)만큼 쌓이면 로봇이 포크로 스택 밑을 받치고
|
||||||
|
누름판(DO0)으로 잡아 통째로 팔레트에 적재한다
|
||||||
|
|
||||||
[스레드 구조]
|
[스레드 구조]
|
||||||
- Flask 요청 스레드 : /robot_start, /one_line_run 요청 안에서 적재 메인 루프가
|
- Flask 요청 스레드 : /robot_start, /one_line_run 요청 안에서 적재 메인 루프가
|
||||||
그대로 돌기 때문에 해당 HTTP 요청은 작업 종료까지 블록된다.
|
그대로 돌기 때문에 해당 HTTP 요청은 작업 종료까지 블록된다.
|
||||||
- get_status_thread : 0.1s 주기로 로봇 상태/센서 폴링, 경광등·컨베이어 제어,
|
- get_status_thread : 0.1s 주기로 로봇 상태/센서 폴링, 경광등·컨베이어 제어,
|
||||||
비상정지(EMO) 해제 후 홈 복귀 처리.
|
비상정지(EMO) 해제 후 홈 복귀 처리.
|
||||||
- stacker_thread : 스태커 실린더로 책을 정렬/압축하는 반복 시퀀스.
|
- stacker_thread : 스태커 리프터/선반 실린더로 책을 하부 적층하는 반복 시퀀스.
|
||||||
|
|
||||||
[진행 상태 저장]
|
[진행 상태 저장]
|
||||||
- static/json/cache.json : 일반 적재 진행 상태 (몇 층/몇 권/현재 적재 높이)
|
- static/json/cache.json : 일반 적재 진행 상태 (몇 층/몇 권/현재 적재 높이)
|
||||||
@@ -136,17 +148,17 @@ IN_POS_POLL_INTERVAL = 0.01 # [신규] is_in_pos() 폴링 주기 (기존: 간
|
|||||||
IDLE_LOOP_INTERVAL = 0.02 # [신규] 메인 루프 유휴 폴링 주기 (기존: 간격 없는 busy-wait)
|
IDLE_LOOP_INTERVAL = 0.02 # [신규] 메인 루프 유휴 폴링 주기 (기존: 간격 없는 busy-wait)
|
||||||
JKS_POLL_INTERVAL = 0.05 # [신규] 높이탐색 JKS 프로그램 종료 폴링 주기
|
JKS_POLL_INTERVAL = 0.05 # [신규] 높이탐색 JKS 프로그램 종료 폴링 주기
|
||||||
# ── 스태커 실린더 시퀀스 대기 (물리 스트로크 시간, 기존값 유지) ──
|
# ── 스태커 실린더 시퀀스 대기 (물리 스트로크 시간, 기존값 유지) ──
|
||||||
STACKER_BACKOFF = 0.5 # [기존 2.0] 책 준비 완료 상태에서 스태커 대기(재확인) 주기
|
STACKER_BACKOFF = 0.5 # [기존 2.0] 스택 픽업 대기(DI1 가려짐) 상태에서 재확인 주기
|
||||||
STACKER_PRE_PRESS_WAIT = 0.5 # 낱권 감지 후 수직 누름 시작 전 대기
|
STACKER_LIFT_START_WAIT = 0.5 # 책 인입 감지 후 리프트 시작 전 대기 (책이 완전히 들어올 시간)
|
||||||
STACKER_PRESS_SETTLE = 0.05 # 수직 누름 후 수평 후퇴 시작 전 대기
|
STACKER_SHELF_OPEN_DELAY = 0.05 # 리프트 시작 후 선반 열기까지 지연
|
||||||
STACKER_SIDE_RETRACT = 1.2 # 수평 실린더 후퇴 유지(다음 책 유입 공간 확보) 시간
|
STACKER_SHELF_OPEN_TIME = 1.2 # 선반 열림 유지 시간 (리프터가 스택을 선반 위로 올리는 동안)
|
||||||
STACKER_SIDE_PUSH = 0.2 # 수평 재전진 후 수직 해제 전 대기
|
STACKER_SHELF_CLOSE_SETTLE = 0.2 # 선반 닫힘 후 리프터 하강 전 대기 (스택 안착)
|
||||||
# ═══════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
# ── 스태커 센서 상태 (get_status_thread 가 0.1s 주기로 갱신) ──────────────
|
# ── 스태커 센서 상태 (get_status_thread 가 0.1s 주기로 갱신) ──────────────
|
||||||
stacker_vertical_cyl_sensor = 0 # DI2: 수직 실린더 '위' 센서
|
stacker_vertical_cyl_sensor = 0 # DI2: 리프터(수직 실린더) '위' 센서
|
||||||
stacker_vertical_cyl_down_sensor = 0 # DI3: 수직 실린더 '아래' 센서
|
stacker_vertical_cyl_down_sensor = 0 # DI3: 리프터(수직 실린더) '아래' 센서
|
||||||
stacker_book_ready_sensor = 0 # DI1: 책 묶음 준비 센서
|
stacker_book_ready_sensor = 0 # DI1: 스택 준비(픽업량 도달) 센서
|
||||||
|
|
||||||
# ── 팔레트별 최대 하강 z (사용자좌표계 기준 거리) ─────────────────────────
|
# ── 팔레트별 최대 하강 z (사용자좌표계 기준 거리) ─────────────────────────
|
||||||
wooden_wheel_pallet_max_z_pos = 864
|
wooden_wheel_pallet_max_z_pos = 864
|
||||||
@@ -767,8 +779,8 @@ def wooden_wheel_pallet():
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# ── 픽업 시작 조건 ──
|
# ── 픽업 시작 조건 ──
|
||||||
# (책 묶음 준비 AND 스태커 수직 실린더 완전 하강 AND 로봇 정상)
|
# (스택 픽업량 도달(DI1) AND 리프터 완전 하강(DI3) AND 로봇 정상)
|
||||||
# 또는 (UI '즉시 픽업' AND 수직 실린더 상승 아님 AND 로봇 정상)
|
# 또는 (UI '즉시 픽업' AND 리프터 상승 상태 아님 AND 로봇 정상)
|
||||||
if (((stacker_book_ready_sensor == 1) and (stacker_vertical_cyl_sensor == 0) and (stacker_vertical_cyl_down_sensor == 1)) and (emergency_stop == 0 or protected_stop_stat == 0) and (power_on_stat == 1 and enabled_stat == 1)) or (((immediately_take_book == 'robot_immediately_take_book') and (stacker_vertical_cyl_sensor == 0)) and (emergency_stop == 0 or protected_stop_stat == 0) and (power_on_stat == 1 and enabled_stat == 1)):
|
if (((stacker_book_ready_sensor == 1) and (stacker_vertical_cyl_sensor == 0) and (stacker_vertical_cyl_down_sensor == 1)) and (emergency_stop == 0 or protected_stop_stat == 0) and (power_on_stat == 1 and enabled_stat == 1)) or (((immediately_take_book == 'robot_immediately_take_book') and (stacker_vertical_cyl_sensor == 0)) and (emergency_stop == 0 or protected_stop_stat == 0) and (power_on_stat == 1 and enabled_stat == 1)):
|
||||||
immediately_take_book = None
|
immediately_take_book = None
|
||||||
|
|
||||||
@@ -1253,8 +1265,8 @@ def iron_wheel_pallet():
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# ── 픽업 시작 조건 ──
|
# ── 픽업 시작 조건 ──
|
||||||
# (책 묶음 준비 AND 스태커 수직 실린더 완전 하강 AND 로봇 정상)
|
# (스택 픽업량 도달(DI1) AND 리프터 완전 하강(DI3) AND 로봇 정상)
|
||||||
# 또는 (UI '즉시 픽업' AND 수직 실린더 상승 아님 AND 로봇 정상)
|
# 또는 (UI '즉시 픽업' AND 리프터 상승 상태 아님 AND 로봇 정상)
|
||||||
if (((stacker_book_ready_sensor == 1) and (stacker_vertical_cyl_sensor == 0) and (stacker_vertical_cyl_down_sensor == 1)) and (emergency_stop == 0 or protected_stop_stat == 0) and (power_on_stat == 1 and enabled_stat == 1)) or (((immediately_take_book == 'robot_immediately_take_book') and (stacker_vertical_cyl_sensor == 0)) and (emergency_stop == 0 or protected_stop_stat == 0) and (power_on_stat == 1 and enabled_stat == 1)):
|
if (((stacker_book_ready_sensor == 1) and (stacker_vertical_cyl_sensor == 0) and (stacker_vertical_cyl_down_sensor == 1)) and (emergency_stop == 0 or protected_stop_stat == 0) and (power_on_stat == 1 and enabled_stat == 1)) or (((immediately_take_book == 'robot_immediately_take_book') and (stacker_vertical_cyl_sensor == 0)) and (emergency_stop == 0 or protected_stop_stat == 0) and (power_on_stat == 1 and enabled_stat == 1)):
|
||||||
immediately_take_book = None
|
immediately_take_book = None
|
||||||
|
|
||||||
@@ -1744,8 +1756,8 @@ def wooden_Flat_pallet():
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# ── 픽업 시작 조건 ──
|
# ── 픽업 시작 조건 ──
|
||||||
# (책 묶음 준비 AND 스태커 수직 실린더 완전 하강 AND 로봇 정상)
|
# (스택 픽업량 도달(DI1) AND 리프터 완전 하강(DI3) AND 로봇 정상)
|
||||||
# 또는 (UI '즉시 픽업' AND 수직 실린더 상승 아님 AND 로봇 정상)
|
# 또는 (UI '즉시 픽업' AND 리프터 상승 상태 아님 AND 로봇 정상)
|
||||||
if (((stacker_book_ready_sensor == 1) and (stacker_vertical_cyl_sensor == 0) and (stacker_vertical_cyl_down_sensor == 1)) and (emergency_stop == 0 or protected_stop_stat == 0) and (power_on_stat == 1 and enabled_stat == 1)) or (((immediately_take_book == 'robot_immediately_take_book') and (stacker_vertical_cyl_sensor == 0)) and (emergency_stop == 0 or protected_stop_stat == 0) and (power_on_stat == 1 and enabled_stat == 1)):
|
if (((stacker_book_ready_sensor == 1) and (stacker_vertical_cyl_sensor == 0) and (stacker_vertical_cyl_down_sensor == 1)) and (emergency_stop == 0 or protected_stop_stat == 0) and (power_on_stat == 1 and enabled_stat == 1)) or (((immediately_take_book == 'robot_immediately_take_book') and (stacker_vertical_cyl_sensor == 0)) and (emergency_stop == 0 or protected_stop_stat == 0) and (power_on_stat == 1 and enabled_stat == 1)):
|
||||||
immediately_take_book = None
|
immediately_take_book = None
|
||||||
|
|
||||||
@@ -2378,8 +2390,8 @@ def one_line_wooden_wheel_pallet():
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# ── 픽업 시작 조건 ──
|
# ── 픽업 시작 조건 ──
|
||||||
# (책 묶음 준비 AND 스태커 수직 실린더 완전 하강 AND 로봇 정상)
|
# (스택 픽업량 도달(DI1) AND 리프터 완전 하강(DI3) AND 로봇 정상)
|
||||||
# 또는 (UI '즉시 픽업' AND 수직 실린더 상승 아님 AND 로봇 정상)
|
# 또는 (UI '즉시 픽업' AND 리프터 상승 상태 아님 AND 로봇 정상)
|
||||||
if (((stacker_book_ready_sensor == 1) and (stacker_vertical_cyl_sensor == 0) and (stacker_vertical_cyl_down_sensor == 1)) and (emergency_stop == 0 or protected_stop_stat == 0) and (power_on_stat == 1 and enabled_stat == 1)) or (((immediately_take_book == 'robot_immediately_take_book') and (stacker_vertical_cyl_sensor == 0)) and (emergency_stop == 0 or protected_stop_stat == 0) and (power_on_stat == 1 and enabled_stat == 1)):
|
if (((stacker_book_ready_sensor == 1) and (stacker_vertical_cyl_sensor == 0) and (stacker_vertical_cyl_down_sensor == 1)) and (emergency_stop == 0 or protected_stop_stat == 0) and (power_on_stat == 1 and enabled_stat == 1)) or (((immediately_take_book == 'robot_immediately_take_book') and (stacker_vertical_cyl_sensor == 0)) and (emergency_stop == 0 or protected_stop_stat == 0) and (power_on_stat == 1 and enabled_stat == 1)):
|
||||||
immediately_take_book = None
|
immediately_take_book = None
|
||||||
|
|
||||||
@@ -2859,8 +2871,8 @@ def one_line_iron_wheel_pallet():
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# ── 픽업 시작 조건 ──
|
# ── 픽업 시작 조건 ──
|
||||||
# (책 묶음 준비 AND 스태커 수직 실린더 완전 하강 AND 로봇 정상)
|
# (스택 픽업량 도달(DI1) AND 리프터 완전 하강(DI3) AND 로봇 정상)
|
||||||
# 또는 (UI '즉시 픽업' AND 수직 실린더 상승 아님 AND 로봇 정상)
|
# 또는 (UI '즉시 픽업' AND 리프터 상승 상태 아님 AND 로봇 정상)
|
||||||
if (((stacker_book_ready_sensor == 1) and (stacker_vertical_cyl_sensor == 0) and (stacker_vertical_cyl_down_sensor == 1)) and (emergency_stop == 0 or protected_stop_stat == 0) and (power_on_stat == 1 and enabled_stat == 1)) or (((immediately_take_book == 'robot_immediately_take_book') and (stacker_vertical_cyl_sensor == 0)) and (emergency_stop == 0 or protected_stop_stat == 0) and (power_on_stat == 1 and enabled_stat == 1)):
|
if (((stacker_book_ready_sensor == 1) and (stacker_vertical_cyl_sensor == 0) and (stacker_vertical_cyl_down_sensor == 1)) and (emergency_stop == 0 or protected_stop_stat == 0) and (power_on_stat == 1 and enabled_stat == 1)) or (((immediately_take_book == 'robot_immediately_take_book') and (stacker_vertical_cyl_sensor == 0)) and (emergency_stop == 0 or protected_stop_stat == 0) and (power_on_stat == 1 and enabled_stat == 1)):
|
||||||
immediately_take_book = None
|
immediately_take_book = None
|
||||||
|
|
||||||
@@ -3251,10 +3263,10 @@ def robot_get_status_realtime():
|
|||||||
emergency_stop = status[1][23] # 비상정지 눌림 여부
|
emergency_stop = status[1][23] # 비상정지 눌림 여부
|
||||||
protected_stop_stat = status[1][5] # 보호정지(충돌 감지) 여부
|
protected_stop_stat = status[1][5] # 보호정지(충돌 감지) 여부
|
||||||
|
|
||||||
current_DI_9_status = status[1][11][0] # 스태커 수직 반사판 센서
|
current_DI_9_status = status[1][11][0] # DI0: 스태커 책 인입 반사판 센서
|
||||||
stacker_book_ready_sensor = status[1][11][1] # 스태커 수평 반사판 센서
|
stacker_book_ready_sensor = status[1][11][1] # DI1: 스태커 스택 준비(픽업량 도달) 센서
|
||||||
stacker_vertical_cyl_sensor = status[1][11][2] # 스태커 수직 실린더 위에 센서
|
stacker_vertical_cyl_sensor = status[1][11][2] # DI2: 스태커 리프터 '위' 센서
|
||||||
stacker_vertical_cyl_down_sensor = status[1][11][3] # 스태커 수직 실린더 아래 센서
|
stacker_vertical_cyl_down_sensor = status[1][11][3] # DI3: 스태커 리프터 '아래' 센서
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -3345,11 +3357,12 @@ def robot_get_status_realtime():
|
|||||||
|
|
||||||
|
|
||||||
def stacker_thread_start_function():
|
def stacker_thread_start_function():
|
||||||
"""스태커(책 정렬대) 실린더 제어 스레드를 시작한다.
|
"""스태커(하부 적층 장치) 실린더 제어 스레드를 시작한다.
|
||||||
|
|
||||||
스태커: 재단기에서 낱권으로 들어오는 책을 수평 실린더로 밀어 정렬하고,
|
동작 원리: 책이 들어오면(DI0) 리프터(coil 0)가 책을 위로 밀어올리고,
|
||||||
낱권 감지(DI0) 시 수직 실린더로 한 번 눌러 압축하는 장치.
|
그 사이 선반(coil 1)이 열렸다 닫히면서 책이 선반 위 스택 맨 아래에 끼워진다.
|
||||||
책 묶음이 완성(DI1=1)되면 로봇이 가져갈 때까지 대기한다.
|
스택이 픽업량(DI1=1)에 도달하면 로봇이 가져갈 때까지 대기한다.
|
||||||
|
(자세한 원리는 파일 상단 [스태커 동작 원리] 참고)
|
||||||
"""
|
"""
|
||||||
global stacker_thread_running, stacker_thread_operator
|
global stacker_thread_running, stacker_thread_operator
|
||||||
|
|
||||||
@@ -3367,27 +3380,27 @@ def stacker_thread_start_function():
|
|||||||
|
|
||||||
while stacker_thread_running:
|
while stacker_thread_running:
|
||||||
try:
|
try:
|
||||||
instrument.write_bit(1, True) # 수평 실린더 전진 유지 (책 정렬 기본 상태)
|
instrument.write_bit(1, True) # 선반 닫힘 유지 (스택 받침 기본 상태)
|
||||||
|
|
||||||
if stacker_book_ready_sensor == 1:
|
if stacker_book_ready_sensor == 1:
|
||||||
# 묶음 완성 → 로봇이 가져갈 때까지 스태커는 쉰다.
|
# 스택 픽업량 도달 → 로봇이 가져갈 때까지 스태커는 쉰다.
|
||||||
# (기존 2.0s → 0.5s: 픽업 직후 스태커 재개가 최대 1.5s 빨라짐)
|
# (기존 2.0s → 0.5s: 픽업 직후 스태커 재개가 최대 1.5s 빨라짐)
|
||||||
time.sleep(STACKER_BACKOFF)
|
time.sleep(STACKER_BACKOFF)
|
||||||
elif current_DI_9_status == 1 and stacker_book_ready_sensor == 0 and (emergency_stop == 0 or protected_stop_stat == 0):
|
elif current_DI_9_status == 1 and stacker_book_ready_sensor == 0 and (emergency_stop == 0 or protected_stop_stat == 0):
|
||||||
# 낱권 감지 → 1회 압축 시퀀스:
|
# 책 인입 감지 → 하부 적층 1사이클:
|
||||||
# 수직 누름 → 수평 후퇴(다음 책 유입 공간 확보) → 수평 재전진 → 수직 해제
|
# 리프터 상승(책+스택을 선반 위로) → 선반 열기 → 선반 닫기 → 리프터 하강
|
||||||
print(f"스태커 스레드 current_DI_9_status: {current_DI_9_status}")
|
print(f"스태커 스레드 current_DI_9_status: {current_DI_9_status}")
|
||||||
time.sleep(STACKER_PRE_PRESS_WAIT)
|
time.sleep(STACKER_LIFT_START_WAIT)
|
||||||
instrument.write_bit(0, True) # 수직 실린더 누름
|
instrument.write_bit(0, True) # 리프터 상승 (책을 위로 밀어올림)
|
||||||
time.sleep(STACKER_PRESS_SETTLE)
|
time.sleep(STACKER_SHELF_OPEN_DELAY)
|
||||||
instrument.write_bit(1, False) # 수평 실린더 후퇴 (다음 책 유입 공간 확보)
|
instrument.write_bit(1, False) # 선반 열기 (책이 선반 높이를 통과하도록)
|
||||||
time.sleep(STACKER_SIDE_RETRACT)
|
time.sleep(STACKER_SHELF_OPEN_TIME)
|
||||||
instrument.write_bit(1, True) # 수평 실린더 재전진 (정렬)
|
instrument.write_bit(1, True) # 선반 닫기 (스택을 다시 받침)
|
||||||
time.sleep(STACKER_SIDE_PUSH)
|
time.sleep(STACKER_SHELF_CLOSE_SETTLE)
|
||||||
instrument.write_bit(0, False) # 수직 실린더 해제
|
instrument.write_bit(0, False) # 리프터 하강 (스택은 선반 위에 걸쳐 남음)
|
||||||
|
|
||||||
if emergency_stop == 1 or protected_stop_stat == 1:
|
if emergency_stop == 1 or protected_stop_stat == 1:
|
||||||
instrument.write_bit(1, False) # 비상 시 수평 실린더 해제
|
instrument.write_bit(1, False) # 비상 시 선반 열림 (실린더 압력 해제)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass # Modbus 일시 오류는 무시하고 다음 주기에 재시도
|
pass # Modbus 일시 오류는 무시하고 다음 주기에 재시도
|
||||||
|
|||||||
18
app.spec
18
app.spec
@@ -1,4 +1,20 @@
|
|||||||
# -*- mode: python ; coding: utf-8 -*-
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
# ═════════════════════════════════════════════════════════════════
|
||||||
|
# 팔레타이징 앱 실행파일 빌드 설정 (PyInstaller)
|
||||||
|
#
|
||||||
|
# [빌드 명령어]
|
||||||
|
# pyinstaller app.spec
|
||||||
|
# → 산출물: dist\app\팔레타이징.exe (더블클릭 실행, dist\app 폴더째 배포)
|
||||||
|
#
|
||||||
|
# [사전 준비 - 빌드용 파이썬에 패키지 설치]
|
||||||
|
# python -m pip install pyinstaller flask waitress pywebview minimalmodbus pyserial
|
||||||
|
#
|
||||||
|
# [참고]
|
||||||
|
# - SDK/jkrc.pyd 는 Stable ABI(python3.dll)라 파이썬 3.x 버전을 크게 안 탐.
|
||||||
|
# 단, pywebview(pythonnet)가 최신 파이썬을 아직 지원 안 하면
|
||||||
|
# Python 3.12 등에서: py -3.12 -m pip install ... / py -3.12 -m PyInstaller app.spec
|
||||||
|
# - 기존 운영 exe는 Python 3.8로 빌드된 것이었음 (2024년 빌드)
|
||||||
|
# ═════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
|
||||||
a = Analysis(
|
a = Analysis(
|
||||||
@@ -21,7 +37,7 @@ exe = EXE(
|
|||||||
a.scripts,
|
a.scripts,
|
||||||
[],
|
[],
|
||||||
exclude_binaries=True,
|
exclude_binaries=True,
|
||||||
name='app',
|
name='팔레타이징', # exe 파일명 (기존에는 name='app'으로 빌드 후 수동으로 이름 변경했었음)
|
||||||
debug=False,
|
debug=False,
|
||||||
bootloader_ignore_signals=False,
|
bootloader_ignore_signals=False,
|
||||||
strip=False,
|
strip=False,
|
||||||
|
|||||||
94
변경내역_20260807.md
Normal file
94
변경내역_20260807.md
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# 팔레타이징 로봇 프로그램 변경 내역서 (인수인계용)
|
||||||
|
|
||||||
|
- **대상 설비**: 책 재단기 라인 팔레타이징 로봇 (JAKA) 제어 프로그램
|
||||||
|
- **작업일**: 2026-08-07
|
||||||
|
- **작업 목적**: ① 사이클 중 불필요한 대기 제거로 택타임 단축 ② 유지보수를 위한 전체 주석·문서화
|
||||||
|
- **변경 파일**: api_routes.py(로봇 제어 전체), app.py(진입점 정리), app.spec(빌드 설정), .gitignore
|
||||||
|
- **중요**: 로봇의 이동 경로·좌표·속도 파라미터는 일절 변경하지 않았음. 대기시간(sleep)과
|
||||||
|
소프트웨어 구조만 변경. **현장 실기 테스트는 미완료 상태이므로 적용 전 반드시 시운전 필요.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 원본 코드 및 변경분 위치
|
||||||
|
|
||||||
|
| 구분 | 위치 |
|
||||||
|
|---|---|
|
||||||
|
| 원본(현장에서 받아온 코드) | git `main` 브랜치 / 로컬 `_backup/` 폴더 |
|
||||||
|
| 이번 변경분 | git `feature/tact-time-optimization` 브랜치 |
|
||||||
|
| 저장소 | https://backend-dev.podosee.com/palletizing_corever/palletizing.git |
|
||||||
|
| 전체 차이 확인 | `git diff main feature/tact-time-optimization` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 대기시간 조정 — 택타임 단축 (책 1권당 약 2.5~3초 단축 예상)
|
||||||
|
|
||||||
|
모든 대기시간은 api_routes.py 상단 **"대기시간 설정 (택타임 튜닝 포인트)" 상수 블록**에
|
||||||
|
모여 있으며, 문제 발생 시 해당 상수 하나만 주석에 적힌 `[기존값]`으로 되돌리면 된다.
|
||||||
|
|
||||||
|
| 항목 | 상수명 | 기존 | 변경 | 비고 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 픽업 진입 전 그리퍼 후퇴 대기 | GRIPPER_RETRACT_WAIT | 2.0초 | 0.5초 | 가장 큰 단축 요소 |
|
||||||
|
| 적재 시 수평 푸셔 유지 대기 | PUSH_HOLD_WAIT | 1.0초 | 0.6초 | |
|
||||||
|
| 포크 인출 후 푸셔 해제 전 대기 | FORK_EXIT_WAIT | 0.5초 | 0.3초 | |
|
||||||
|
| 적재 후 수직 그리퍼 해제 대기 | RELEASE_WAIT | 0.3/0.2초 | 0.2초 통일 | 0.2는 짝수층에서 기존 검증값 |
|
||||||
|
| 모션 도달확인 가드 | IN_POS_GUARD_TIME | 0.1초 | 0.05초 | 1권당 8~10회 호출됨 |
|
||||||
|
| 스태커 픽업 대기 재확인 주기 | STACKER_BACKOFF | 2.0초 | 0.5초 | 픽업 후 스태커 재개 빨라짐 |
|
||||||
|
|
||||||
|
**변경하지 않고 유지한 값** (물리 동작과 직결되어 보수적으로 유지, 추가 튜닝 후보):
|
||||||
|
파지 대기(홀수층 0.3/짝수층 0.1), 책 흔들림 안정화(0.3), 그리드 진입 저속 파라미터
|
||||||
|
(grid_speed/grid_acc — 책 미끄러짐 방지 목적), 스태커 실린더 스트로크 대기 4종.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 소프트웨어 구조 개선 (멈칫/지연 원인 제거)
|
||||||
|
|
||||||
|
1. **busy-wait 제거**: 메인 루프·도달확인(is_in_pose)·높이탐색 폴링이 대기 간격 없이
|
||||||
|
돌면서 CPU를 점유해, 센서값을 갱신하는 상태 폴링 스레드가 밀리던 문제 → 폴링 간격
|
||||||
|
(10~20ms) 추가로 센서 반응성 개선.
|
||||||
|
2. **높이탐색 루프의 매회 print 제거**: 권당 수백 회 콘솔 출력이 있었고, 윈도우 콘솔이
|
||||||
|
블록되면(마우스 드래그 등) 로봇 루프 전체가 멈추는 위험이 있었음.
|
||||||
|
3. **경광등 신호 전송 최적화**: 같은 값을 0.1초마다 3회씩 재전송하던 것을 값이 바뀔
|
||||||
|
때만 전송(3초마다 1회 재전송으로 자기복구 유지) → 로봇 컨트롤러 통신 부하 제거.
|
||||||
|
4. **통신 오류 보호(잠재 결함 수정)**: 도달확인·관절값 수신이 통신 오류일 때 오류값을
|
||||||
|
그대로 믿고 진행하던 부분을 정상 수신까지 재시도하도록 수정 (적재 높이 오염 방지).
|
||||||
|
5. **비상정지 중 통신 폭주 방지**: 정지 상태에서 무한 고속 반복되던 컨트롤러 호출에 대기 추가.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 주석·문서화 (동작과 무관)
|
||||||
|
|
||||||
|
- api_routes.py 상단에 시스템 개요, 로봇 I/O 맵(DO0~DO5, DI0~DI3), 스태커 동작 원리
|
||||||
|
(하부 적층 방식), 스레드 구조, 진행상태 파일(cache.json/oneLine.json) 설명 추가.
|
||||||
|
- 전 함수 docstring 및 픽업/적재 시퀀스 단계별 주석 추가.
|
||||||
|
- 스태커 관련 명칭을 실제 구조(리프터/선반) 기준으로 정정, 오타 수정.
|
||||||
|
- app.py의 방치된 주석 코드 블록 제거(원본은 git main에 보존).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 실행파일 빌드 방법 (app.spec 상단에도 동일 내용 주석 있음)
|
||||||
|
|
||||||
|
```
|
||||||
|
python -m pip install pyinstaller flask waitress pywebview minimalmodbus pyserial
|
||||||
|
pyinstaller app.spec
|
||||||
|
```
|
||||||
|
- 산출물: `dist\app\팔레타이징.exe` — **dist\app 폴더째** 현장 PC에 복사 후 exe 더블클릭 실행.
|
||||||
|
- 기존 운영 exe는 Python 3.8 빌드였음. SDK의 jkrc.pyd는 Stable ABI라 파이썬 버전 무관하나,
|
||||||
|
pywebview 설치가 최신 파이썬에서 실패하면 Python 3.12로 빌드할 것.
|
||||||
|
- **현 운영 exe는 변경 전 코드로 빌드된 것이므로, 이번 변경을 현장 반영하려면 재빌드 필수.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 적용 절차 및 이상 발생 시 조치 (인수자 필독)
|
||||||
|
|
||||||
|
1. 재빌드 → dist\app 폴더 교체 → 소량으로 시운전:
|
||||||
|
홀수층 1권 → 짝수층(그리퍼 회전) 1권 → 비상정지/해제 복구 → 경광등 3색 확인.
|
||||||
|
2. 증상별 복원 (api_routes.py 상단 상수 수정만으로 해결):
|
||||||
|
|
||||||
|
| 증상 | 조치 |
|
||||||
|
|---|---|
|
||||||
|
| 스태커 진입 시 그리퍼 실린더 간섭 | GRIPPER_RETRACT_WAIT 0.5 → 1.0 → 2.0 순서로 복원 |
|
||||||
|
| 적재 후 포크에 책이 딸려 올라옴 | PUSH_HOLD_WAIT 0.6 → 1.0, FORK_EXIT_WAIT 0.3 → 0.5 |
|
||||||
|
| 동작이 겹치거나 불안정 | IN_POS_GUARD_TIME 0.05 → 0.1 |
|
||||||
|
| 픽업 시 책을 놓침 | GRIP_CLAMP_WAIT_ODD/EVEN 값 상향 |
|
||||||
|
|
||||||
|
3. 완전 원복: git `main` 브랜치(또는 `_backup/` 원본) 코드로 교체 후 재빌드.
|
||||||
Reference in New Issue
Block a user