@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.6 → 0.1.2-dev.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,24 +1,114 @@
1
+ """
2
+ Muscat plugin backend — __PLUGIN_NAME__
3
+
4
+ 플러그인 UI(브라우저)와 Socket.IO로 통신한다.
5
+ `muscat-pk dev:back` 또는 `python main.py`로 실행.
6
+
7
+ 토픽 자동 네임스페이스:
8
+ "sensor/data" → plugin/__PLUGIN_ID__/sensor/data (자동)
9
+ "/broadcast/x" → broadcast/x (앞 / = 절대경로)
10
+ """
11
+
1
12
  import asyncio
13
+ import socketio
14
+ from aiohttp import web
15
+
16
+ sio = socketio.AsyncServer(async_mode="aiohttp", cors_allowed_origins="*")
17
+ app = web.Application()
18
+ sio.attach(app)
19
+
20
+ PLUGIN_ID = "__PLUGIN_ID__"
21
+ _PREFIX = f"plugin/{PLUGIN_ID}"
22
+
23
+
24
+ def _topic(relative: str) -> str:
25
+ """상대 토픽 → 전체 네임스페이스 토픽."""
26
+ return relative if relative.startswith("/") else f"{_PREFIX}/{relative}"
27
+
28
+
29
+ # ── 구독·쿼리어블 핸들러 저장소 ────────────────────────────────────────────────
30
+ _sub_handlers: dict[str, list] = {}
31
+ _queryable_handlers: dict[str, object] = {}
32
+ _serve_pending: dict[str, asyncio.Future] = {}
33
+
34
+
35
+ # ── Socket.IO 이벤트 ───────────────────────────────────────────────────────────
36
+
37
+ @sio.event
38
+ async def sub(sid, data):
39
+ """브라우저가 토픽을 구독."""
40
+ await sio.enter_room(sid, f"t:{data['topic']}")
41
+
42
+
43
+ @sio.event
44
+ async def unsub(sid, data):
45
+ await sio.leave_room(sid, f"t:{data['topic']}")
46
+
47
+
48
+ @sio.event
49
+ async def pub(sid, data):
50
+ """브라우저가 발행 → Python 구독 핸들러로 전달."""
51
+ topic = data.get("topic", "")
52
+ payload = data.get("payload")
53
+ for handler in _sub_handlers.get(topic, []):
54
+ asyncio.create_task(handler(payload))
55
+
56
+
57
+ @sio.event
58
+ async def action(sid, data):
59
+ """브라우저 rb.action() 호출 → Python queryable 핸들러 → ack 반환."""
60
+ topic = data.get("topic", "")
61
+ handler = _queryable_handlers.get(topic)
62
+ if handler:
63
+ return await handler(data.get("payload"))
64
+
65
+
66
+ @sio.event
67
+ async def serve_reply(sid, data):
68
+ """브라우저가 serve_call에 응답."""
69
+ fut = _serve_pending.pop(data.get("id", ""), None)
70
+ if fut and not fut.done():
71
+ fut.set_result(data.get("payload"))
72
+
73
+
74
+ # ── 헬퍼 데코레이터 ────────────────────────────────────────────────────────────
75
+
76
+ def subscribe(relative_topic: str):
77
+ """브라우저 → Python 메시지 구독 핸들러 등록."""
78
+ def decorator(fn):
79
+ _sub_handlers.setdefault(_topic(relative_topic), []).append(fn)
80
+ return fn
81
+ return decorator
82
+
83
+
84
+ def queryable(relative_topic: str):
85
+ """브라우저 rb.action() 호출을 처리할 핸들러 등록."""
86
+ def decorator(fn):
87
+ _queryable_handlers[_topic(relative_topic)] = fn
88
+ return fn
89
+ return decorator
2
90
 
3
- from rainbow_rb_zenoh import ZenohRouter
4
91
 
5
- router = ZenohRouter(prefix="plugin/__PLUGIN_ID__")
92
+ async def publish(relative_topic: str, payload) -> None:
93
+ """Python → 브라우저 구독자에게 메시지 발행."""
94
+ full = _topic(relative_topic)
95
+ await sio.emit("msg", {"topic": full, "payload": payload}, room=f"t:{full}")
6
96
 
7
97
 
8
- @router.subscribe("sensor/data")
9
- async def on_sensor(*, dict_payload, **_):
10
- print(f"[__PLUGIN_NAME__] received: {dict_payload}")
98
+ # ── 플러그인 로직 ──────────────────────────────────────────────────────────────
11
99
 
100
+ @subscribe("sensor/data")
101
+ async def on_sensor_data(payload):
102
+ print(f"[__PLUGIN_NAME__] sensor/data: {payload}")
12
103
 
13
- @router.queryable("get-status")
14
- async def get_status(req=None, **_):
15
- return {"ok": True}
16
104
 
105
+ @queryable("get-status")
106
+ async def get_status(payload=None):
107
+ return {"ok": True, "plugin": "__PLUGIN_NAME__"}
17
108
 
18
- async def main():
19
- await router.startup()
20
- await asyncio.Event().wait()
21
109
 
110
+ # ── 진입점 ─────────────────────────────────────────────────────────────────────
22
111
 
23
112
  if __name__ == "__main__":
24
- asyncio.run(main())
113
+ print("[__PLUGIN_NAME__] Socket.IO server starting on :10000")
114
+ web.run_app(app, host="0.0.0.0", port=10000)
@@ -1,2 +1,3 @@
1
1
  rainbow-rb-sdk
2
2
  rainbow-rb-flat-buffers
3
+ python-socketio[aiohttp]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rainbow-robotics/muscat-plugin-kit",
3
- "version": "0.1.2-dev.6",
3
+ "version": "0.1.2-dev.7",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=20"
@@ -31,7 +31,7 @@
31
31
  "@types/react-dom": "^19.0.4"
32
32
  },
33
33
  "dependencies": {
34
- "@rainbow-robotics/preview-host": "*",
34
+ "@rainbow-robotics/preview-host": "0.1.2-dev.7",
35
35
  "esbuild": "^0.25.0"
36
36
  },
37
37
  "optionalDependencies": {