Robot Scripting Language (RSL) - 카미봇 미니 DSL: 파서/검증기/async 인터프리터
Project description
pykamrsl — Kamibot Pi 미니 DSL (RSL)
RSL (Robot Scripting Language) — 카미봇 파이(Kamibot Pi) 로봇을 제어하기 위한
작은 도메인 특화 언어와 그 실행 도구체인. parse → validate → interpret 3 단
파이프라인을 무의존성(dependencies = []) 으로 제공한다.
pykamrsl 자체는 시리얼 포트를 열지 않는다 — 인터프리터는 사용자가 주입한
비동기 도구 사전(dict[str, AsyncTool]) 을 디스패치한다.
실제 카미봇 조작은 LangChain BaseTool, MCP 서버, 또는 직접 만든 stub 어느
백엔드로도 갈아 끼울 수 있다.
설치
pip install pykamirsl
| 이름 | 용도 |
|---|---|
배포명 (pykamirsl) |
pip install 시 입력 |
임포트명 (pykamrsl) |
코드에서 import pykamrsl |
LangChain BaseTool 어댑터와 같이 쓸 거라면 선택 의존성을 함께 설치:
pip install "pykamirsl[langchain]"
5 분 시작 — 하드웨어 없이
FakeTool 만 있으면 카미봇이 없어도 전 파이프라인을 그대로 돌려볼 수 있다.
import asyncio
from pykamrsl import parse, validate, RSLInterpreter
# 1) RSL 스크립트
source = """
BEGIN MISSION "장애물 회피"
SET threshold = 30
SENSE DISTANCE AS d
IF d > threshold:
MOVE BACKWARD CM=10
BEEP
SIGNAL "장애물 감지 — 후진 d={d}"
ELSE:
MOVE FORWARD CM=20
END IF
END MISSION
"""
# 2) parse: 텍스트 → Mission AST
mission = parse(source)
# 3) validate: 값 범위 + 변수 정의 정적 검증
errors = validate(mission)
print("validation errors:", errors) # [] = 통과
# 4) 도구 stub (실제 환경에선 LangChain BaseTool / MCP 어댑터로 교체)
class FakeTool:
def __init__(self, name, ret="ok"):
self.name = name
self._ret = ret
async def ainvoke(self, args):
print(f" → {self.name}({args})")
return self._ret
tools = {
"move_forward_cm": FakeTool("move_forward_cm"),
"move_backward_cm": FakeTool("move_backward_cm"),
"play_beep": FakeTool("play_beep"),
# SENSE 결과는 dict 또는 JSON 문자열로 반환 — 인터프리터가 알아서 디코드.
# DISTANCE 는 min(left, right) 가 변수에 들어감 → 여기선 d=50 → IF 분기.
"read_object_distance_raw": FakeTool("read_object_distance_raw",
{"left": 50, "right": 80}),
}
# 5) 인터프리터 실행
log = asyncio.run(RSLInterpreter(tools).arun(mission))
print("\n".join(log))
출력 예:
validation errors: []
[CMD] read_object_distance_raw()
[CMD] move_backward_cm(distance_cm=10)
[CMD] play_beep()
=== 미션 시작: 장애물 회피 ===
센서 DISTANCE → d=50
ok
ok
SIGNAL: 장애물 감지 — 후진 d=50
=== 미션 종료 ===
[CMD]줄은 인터프리터가 콘솔로 echo 하는 호출 시그니처. 끄려면RSLInterpreter(tools, echo_commands=False).ok두 줄은FakeTool의 반환값 — 실제 환경에서는 도구가 돌려준 한국어 안내 문자열이 그대로 로그에 들어간다.SENSE DISTANCE는min(left, right)를 변수에 저장한다 (d=50).
패키지 표면
from pykamrsl import (
parse, ParseError, # 텍스트 → Mission AST
validate, ValidationError, # AST 의미·범위 검증
RSLInterpreter, RuntimeRSLError, MAX_LOOP_ITERS,
SYSTEM_PROMPT, build_retry_message,
)
pykamrsl.ast_nodes 도 export 됨: Mission, Assign, Move, Turn, Sense,
Signal, If, Loop, Condition, IntLit, StrLit, VarRef 등.
RSL 문법 한눈에 보기
mission := BEGIN MISSION "title" ... END MISSION
statement := assign | move | turn | stop | wait | led | sound
| draw | sense | signal | if_block | loop_block | repeat_block
assign := SET name = value
move := MOVE FORWARD|BACKWARD CM = v
| MOVE FORWARD|BACKWARD SECONDS = v
| MOVE FORWARD|BACKWARD SPEED = v # 연속 이동 — STOP 전까지
turn := TURN LEFT|RIGHT ANGLE = v
stop := STOP
wait := WAIT SECONDS = v
led := LED RGB r, g, b
| LED PRESET n
sound := BEEP | MELODY SCALE = s SECONDS = t
draw := DRAW TRIANGLE|RECTANGLE|PENTAGON|HEXAGON|STAR|CIRCLE SIZE = v
sense := SENSE DISTANCE|LINE_LEFT|LINE_CENTER|LINE_RIGHT|COLOR|BATTERY AS var
signal := SIGNAL "message with {var} interpolation"
if_block := IF cond:
...
(ELSE:
...)?
END IF
loop_block := LOOP UNTIL|WHILE cond:
...
END LOOP
repeat_block := REPEAT count TIMES:
...
END REPEAT
cond := value <|>|<=|>=|==|!= value
| var # 단일 변수 → truthy 검사
value := 정수 | "문자열" | var
- 키워드는 모두 영어 대문자. 변수명은
[a-z0-9_]+. - 코멘트는
# ...(라인 끝까지). - 블록 종료는 명시적
END IF/END LOOP/END MISSION— 들여쓰기 의존 없음.
수치 허용 범위 (검증기)
| 매개변수 | 범위 |
|---|---|
speed |
0 ~ 100 |
cm |
1 ~ 200 |
seconds (이동/대기) |
1 ~ 30 |
degrees |
1 ~ 360 |
| RGB 채널 | 0 ~ 255 |
LED PRESET |
0 ~ 8 (0=꺼짐, 1=빨강, 2=주황, 3=노랑, 4=초록, 5=파랑, 6=하늘, 7=보라, 8=흰색) |
DRAW SIZE |
5 ~ 50 |
MELODY SCALE / SECONDS |
1 ~ 200 / 1 ~ 60 |
REPEAT count |
1 ~ 100 (= MAX_LOOP_ITERS) |
리터럴이 범위를 벗어나면 validate() 가 에러 메시지를 리스트로 반환한다.
변수에 담긴 값은 런타임에 인터프리터가 검사한다.
도구 인터페이스 (인터프리터 백엔드)
RSLInterpreter(tools_by_name) 의 tools_by_name: dict[str, AsyncTool] 은
다음 구조적 인터페이스만 만족하면 된다. langchain_core.BaseTool 인스턴스가
자동으로 만족한다 — langchain-mcp-adapters 가 돌려주는 도구도 그대로 주입 가능.
from typing import Protocol, Any
class AsyncTool(Protocol):
name: str
async def ainvoke(self, args: dict) -> Any: ...
인터프리터가 호출하는 도구 이름
| RSL 문장 | 도구 이름 | 인자 | 비고 |
|---|---|---|---|
MOVE FORWARD CM=v |
move_forward_cm |
distance_cm |
|
MOVE BACKWARD CM=v |
move_backward_cm |
distance_cm |
|
MOVE * SECONDS=v |
move_*_seconds |
seconds |
|
MOVE * SPEED=v |
drive_*_continuous |
speed |
연속 이동 |
TURN LEFT/RIGHT |
turn_left_degrees / turn_right_degrees |
degrees |
|
STOP |
stop_now |
— | |
WAIT SECONDS=v |
wait_seconds |
seconds |
|
LED RGB r, g, b |
set_led_rgb |
r, g, b |
|
LED PRESET n |
set_led_preset |
preset_index |
|
BEEP |
play_beep |
— | |
MELODY SCALE=s SECONDS=t |
play_melody_note |
scale, seconds |
|
DRAW <shape> SIZE=v |
draw_<shape> |
size |
shape ∈ {tri,rect,penta,hexa,star,circle} 의 풀네임 |
SENSE DISTANCE |
read_object_distance_raw |
— | 반환 {"left": int, "right": int} |
SENSE LINE_* |
read_line_sensors_raw |
— | 반환 {"left": int, "center": int, "right": int} |
SENSE COLOR |
read_color_index_raw |
— | 반환 정수 |
SENSE BATTERY |
read_battery_level_raw |
— | 반환 정수 |
센서 도구의 dict / int 는 그대로 돌려줘도 되고 JSON 문자열로 직렬화해서
돌려줘도 된다 — 인터프리터의 _to_text / _coerce_dict / _coerce_int 가
양쪽을 모두 처리한다.
LLM 으로 RSL 자동 생성 (선택)
자연어 한국어 명령 → RSL 변환을 LLM 에 맡길 때 쓰는 시스템 프롬프트가 패키지에 포함돼 있다. few-shot 예시 3 개와 출력 규칙이 들어 있다.
from pykamrsl import SYSTEM_PROMPT, build_retry_message
from openai import OpenAI # 예시 — 다른 모델 OK
client = OpenAI()
resp = client.chat.completions.create(
model="gpt-4o-mini",
temperature=0,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": "앞으로 20cm 가고 LED 를 파란색으로 켜줘"},
],
)
source = resp.choices[0].message.content
# 이후 parse → validate → RSLInterpreter.arun 로 흘리면 끝.
# validate 가 에러를 돌려주면 build_retry_message(errors) 로 LLM 에 재시도 요청 가능.
SIGNAL 의 {var} 보간
SIGNAL "장애물 감지 — d={d}, threshold={threshold}" 는 실행 시점의 변수
값으로 치환된다. 정의되지 않은 변수는 원본 그대로 남는다.
V1 알려진 제약
- 산술/논리 연산자 없음 —
+ - * / AND OR NOT미지원. 비교 6 종< > <= >= == !=만. - 변수에 상수만 대입 가능 (
SET x = 30✓,SET x = y + 1✗). LOOP본문은 최대 100 회 실행 후 자동 종료 —SIGNAL: loop_iteration_cap_reached가 로그에 남음.- 단순 N 회 반복은
REPEAT N TIMES: … END REPEAT를 사용한다. 본문은 카운터 변수를 노출하지 않으며N은 1~100. 변수N이 100 을 넘으면 런타임에 100 으로 잘리고SIGNAL: repeat_count_clamped가 로그에 남음.
더 큰 예제
저장소의 examples/ 디렉토리에는 다음을 포함한 end-to-end LangGraph 에이전트 예제가 들어 있다:
- LLM → RSL → MCP 서버 → 카미봇 통합 흐름
- MCP 서버 (
kamibot_mcp_server.py) — 카미봇 도구 24 종을 stdio 로 노출 KAMIBOT_MOCK=true로 하드웨어 없이 통합 테스트
pip install "pykamirsl[langchain]" langchain-openai langgraph langchain-mcp-adapters
python -m examples.main
라이선스
MIT. 학습용으로 자유롭게 수정·재배포 가능.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pykamirsl-0.1.2.tar.gz.
File metadata
- Download URL: pykamirsl-0.1.2.tar.gz
- Upload date:
- Size: 22.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e4b8e4ece4def7657ea3ab74f634ff511e90c4bdf1eb740abc135fe83798895b
|
|
| MD5 |
a8da4188ca4f596d4589aae53cbaeb25
|
|
| BLAKE2b-256 |
5d875241fece90e1700283c9bc94decb697e091e561e8fb22b9c15e06b659dde
|
File details
Details for the file pykamirsl-0.1.2-py3-none-any.whl.
File metadata
- Download URL: pykamirsl-0.1.2-py3-none-any.whl
- Upload date:
- Size: 22.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
521ddb9039fd4417319474658221877fae911ff5086535f9831235da01a5f042
|
|
| MD5 |
d5ba7ca40ccdd3ab2de1edc2e27e6d61
|
|
| BLAKE2b-256 |
1103db6c4d36971713cca7e0e51a9cf8a6896eee37d55f707ae2501bca50dd46
|