+
## 构建 CodeLab Adapter 插件
@@ -32,9 +32,10 @@ from codelab_adapter.core_extension import Extension
class RobotXYExtension(Extension):
+ NODE_ID = "eim/robot"
+ s
def __init__(self):
super().__init__()
- self.NODE_ID = "eim/robot"
def extension_message_handle(self, topic, payload):
self.logger.debug(f'the message payload from scratch: {payload}')
@@ -56,7 +57,7 @@ export = RobotXYExtension
## 开始测试
如果你对如何运行 CodeLab Adapter extension 不熟悉,请参考 [hello world](/dev_guide/helloworld/)。
-
+
## json message from CodeLab Adapter to Scratch3
@@ -74,9 +75,10 @@ from codelab_adapter.core_extension import Extension
class RobotXYExtension(Extension):
+ NODE_ID = "eim/robot"
+
def __init__(self):
super().__init__()
- self.NODE_ID = "eim/robot"
def extension_message_handle(self, topic, payload):
self.logger.debug(f'the message payload from scratch: {payload}')
@@ -101,7 +103,7 @@ export = RobotXYExtension
重新勾选`extension_robot_xy`插件,现在你可以在 Scratch 中收到 CodeLab Adapte 传过来的 json 数据了!
-
+
在 Scratch 一侧,使用 json 拓展来解析传递过来的消息。
diff --git a/docs/dev_guide/sync-message.md b/docs/dev_guide/sync-message.md
index 0453bd8..1747989 100644
--- a/docs/dev_guide/sync-message.md
+++ b/docs/dev_guide/sync-message.md
@@ -18,7 +18,7 @@
我们如何在异步中实现,同步模式呢?策略是使用 message_id。
-目前 [Scratch EIM](https://github.com/CodeLabClub/scratch3_eim/blob/v2/index.js#L290) 已经支持同步风格的积木(阻塞风格)。
+目前 [Scratch EIM](https://github.com/CodeLabClub/scratch3_eim/blob/v3/index.js) 已经支持同步风格的积木(阻塞风格)。
## 实现
这些同步风格的积木需要与同步风格的 CodeLab Adapter 插件一起使用。让我们来实现它。
@@ -61,7 +61,7 @@ export = SyncHelloWorldExtension
通过与 [hello world 教程](/dev_guide/helloworld/)的对比,可以看出同步消息与异步消息在 CodeLab Adapter 插件一侧的区别:通过返回来自 Scratch 的消息中携带的 message_id(message_id 在 payload 中,通过观察日志,可以看到 payload 内部细节)。让请求者得知当前消息被响应了。
-同步消息与异步消息,在 Scratch 插件一侧的区别表现为不同的积木(是否`wait/等待`),js 代码层面的差异表现在:[发送消息的函数不同](https://github.com/CodeLabClub/scratch3_eim/blob/v2/index.js#L290),这部分你可以直接使用 EIM 插件,可以不做深究。
+同步消息与异步消息,在 Scratch 插件一侧的区别表现为不同的积木(是否`wait/等待`),js 代码层面的差异表现在:[发送消息的函数不同](https://github.com/CodeLabClub/scratch3_eim/blob/v3/index.js),这部分你可以直接使用 EIM 插件,可以不做深究。
刷新 Web UI,点击运行`extension_hello_world.py`,接着你就可以在 Scratch 中与你的插件交互了。
@@ -77,4 +77,4 @@ export = SyncHelloWorldExtension
# 参考:
-* [codelab-adapter 与应答模式](https://blog.just4fun.site/codelab-adapter-req-rep.html)
+* [codelab-adapter 与应答模式](https://wwj718.github.io/codelab-adapter-req-rep.html)
diff --git "a/docs/dev_guide/\345\256\232\345\210\266\344\270\216\345\210\206\345\217\221.md" "b/docs/dev_guide/\345\256\232\345\210\266\344\270\216\345\210\206\345\217\221.md"
index 063a634..fea552b 100644
--- "a/docs/dev_guide/\345\256\232\345\210\266\344\270\216\345\210\206\345\217\221.md"
+++ "b/docs/dev_guide/\345\256\232\345\210\266\344\270\216\345\210\206\345\217\221.md"
@@ -1,20 +1,134 @@
# 定制与分发
-v3.4 版本之后(近期发布),引入了一个钩子(hook): Adapter 在启动时,将加载运行与 Adapter 处于同一目录下的`app_settings.py`文件。
+!!! 提醒
+ 建议 `Adapter >= 4.7.2`, 否则做自定义修改时,可能会遇到一些稳定性问题
+
+Adapter 目前支持用户二次分发, 希望提供给用户更多自由。
+
+一种典型应用场景是开发者/教育者为用户提供定制化的 Adapter(包括Python社区第三方库、自定义 Adapter扩展、数据和内容)。
+
+!!! 提醒
+ 目前北京王府国际学校在使用的 codelab-adapter-4_4_1-AI-alpha 基于这个机制, codelab-adapter-4_4_1-AI-alpha 内置了 Cozmo SDK案例数据、Tensorflow、notebooks教程,目前由尚老师维护。
+
+# 添加 notebooks
+* macOS
+ * 将自定义的目录或文件,放置在 `codelab-adapter-4_5_0-mac.app/Contents/Resources/app/codelab_adapter/notebooks`
+* Windows
+ * 将自定义的目录或文件,放置在 `CodeLab-Adapter\src\app\codelab_adapter\notebooks`
+
+# 添加 扩展(nodes/extensions)
+* macOS
+ * 将自定义插件,放置在 `codelab-adapter-4_5_0-mac.app/Contents/Resources/app/codelab_adapter/nodes`(或者extensions)
+* Windows
+ * 将自定义插件,放置在 `CodeLab-Adapter\src\app\codelab_adapter\nodes`(或者extensions)
+
+如何开发自己的扩展,可参考[最佳实践](/dev_guide/最佳实践/), 你也可以浏览[内置的扩展](https://github.com/CodeLabClub/codelab_adapter_extensions)
+
+# 添加 Python社区第三方库
+参考[安装第三方库](https://adapter.codelab.club/extension_guide/jupyterlab/#_2)
+
+# 修改 UI
+Adapter 的 WebUI 也是可以修改的。甚至可以自定义入口地址(通过修改hook文件: `app_settings.py`)。
+
+UI 相关的文件放在 src 目录。
+
+* macOS
+ * 将自定义插件,放置在 `codelab-adapter-4_5_0-mac.app/Contents/Resources/app/codelab_adapter/src`(或者extensions)
+* Windows
+ * 将自定义插件,放置在 `CodeLab-Adapter\src\app\codelab_adapter\src`(或者extensions)
+
+
+!!! debug技巧
+ 你可以在Adapter运行的时候,打开[Adapter 主目录](/user_guide/FAQ/#adapter),修改 src 目录(静态资源目录)里的文件,刷新页面即刻生效,修改完之后,再放入到Adapter软件包里
+
+# 软件更新提醒
+
+配置参数: `LATEST_VERSION`, 默认值是 `https://adapter.codelab.club/about/latest_version.json`。 是一个 json api 地址,软件在每次启动时会查询这个接口. 以下是一个例子:
+
+```json
+{
+ "version": "4.8.0"
+}
+```
+
+
+
+# 增量更新
+典型的用例是,用户安装过 Adapter 之后,可以动态更新软件和数据包,诸如新发布的 notebooks 课程或新插件,甚至对 UI 的升级,避免重新下载新的 Adapter ,这对于一些网络不便利的用户(学校、落后山区、部分机构)大有帮助。
+
+配置参数: `INCREMENTAL_UPDATE_PACKAGE` 允许你使用自己的 codelab_adapter_client 发行版。
+
+置于如何放置新的数据,可以参考 [codelab_adapter_client 目录结构](https://github.com/CodeLabClub/codelab_adapter_client_python/tree/master/codelab_adapter_client/data)
+
+# FAQ
+
+### 定制未生效
+定制完后需要 **更新扩展(下文)**,或者手动删除 [Adapter 主目录](/user_guide/FAQ/#adapter)
+
+### 如何分发
+Adapter 是绿色软件(免装),拷贝分发即可.
+
+建议加上 自定义版本的哈希值,方便用户做安全校验。 参考[Codelab Adapter下载链接](/get_start/gs_install/#download)
+
+### 更新扩展
+如果用户之前安装过Adapter,需要更新扩展
+
+
+
+### 静态资源
+如果你需要使用自定义静态资源(诸如图片),建议将其放到 src 目录里,之后引用它:
+
+```py
+codelab_adapter_dir = pathlib.Path.home() / "codelab_adapter"
+app_icon = codelab_adapter_dir / 'src' / "app.png"
+```
+
+### 代码安全
+你可能不希望开放源码。
+
+诸如你可能是一家硬件公司,不希望自家的sdk以开放代码分发。
+
+有很多种做法,简单的做法是使用编译型语言(诸如Nim、C、Rust、Go...)写好sdk,放置在Adapter合适的目录里,然后在Adapter插件中调用它(FFI之类的机制)。
+
+如果你希望总是工作在Python技术栈,可以试试 [Cython](https://cython.org/),如果你希望在Adapter Python插件直接import 使用 Cython build后的文件,开发环境的Python版本请尽可能与[Adapter宿主环境](https://adapter.codelab.club/user_guide/FAQ/#python-codelab-adapter-45)保持一致。
+
+可以参考 brainCo 插件。
+
+以上方法也适用于与加解密有关的项目,考虑到密钥可能被逆向,建议使用非对称加密。
+
+### 优化软件包(清理碎片小文件)
+进入软件包内部,清理缓存, 使下载和解压缩速度加快
+
+```bash
+find . | grep -E "(__pycache__|\.pyc$)" | xargs rm -rf
+```
+
+
\ No newline at end of file
diff --git "a/docs/dev_guide/\346\216\245\345\205\245\347\254\254\344\270\211\346\226\271\345\271\263\345\217\260.md" "b/docs/dev_guide/\346\216\245\345\205\245\347\254\254\344\270\211\346\226\271\345\271\263\345\217\260.md"
index 4a8a72d..1122530 100644
--- "a/docs/dev_guide/\346\216\245\345\205\245\347\254\254\344\270\211\346\226\271\345\271\263\345\217\260.md"
+++ "b/docs/dev_guide/\346\216\245\345\205\245\347\254\254\344\270\211\346\226\271\345\271\263\345\217\260.md"
@@ -8,7 +8,7 @@ CodeLab Adapter v3 允许 [codelab.club](https://www.codelab.club/) 的合作方
## 教程
-我们假设你已经读了[创建你的第一个 Scratch 3.0 Extension](https://blog.just4fun.site/create-first-Scratch3-Extension.html),如果没有,阅读完再回来。最好跟着文章操作一遍。
+我们假设你已经读了[创建你的第一个 Scratch 3.0 Extension](https://wwj718.github.io/create-first-Scratch3-Extension.html),如果没有,阅读完再回来。最好跟着文章操作一遍。
### 在 Scratch 3.0 中创建 EIM Extension
@@ -67,5 +67,5 @@ CodeLab Adapter extension_eim 的源码,我们也已经更新到 GitHub 上:
## 参考
-- [创建你的第一个 Scratch 3.0 Extension](https://blog.just4fun.site/create-first-Scratch3-Extension.html)
-- [CodeLab Adapter 可以支持其他平台吗?](https://adapter.codelab.club/user_guide/FAQ/#codelab-adapter)
+- [创建你的第一个 Scratch 3.0 Extension](https://wwj718.github.io/create-first-Scratch3-Extension.html)
+- [CodeLab Adapter 可以支持其他平台吗?](/user_guide/FAQ/#codelab-adapter)
diff --git "a/docs/dev_guide/\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/docs/dev_guide/\346\234\200\344\275\263\345\256\236\350\267\265.md"
new file mode 100644
index 0000000..48fd766
--- /dev/null
+++ "b/docs/dev_guide/\346\234\200\344\275\263\345\256\236\350\267\265.md"
@@ -0,0 +1,17 @@
+# 最佳实践
+我们整理出3个插件,作为最佳实践的例子:
+
+* Python插件(Extension):
+ * 是一个 [Adapter Extension](/dev_guide/helloworld/)
+ * Adapter extension: [extension_python.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/extensions_v3/extension_python.py)
+ * Scratch extension: [scratch3_python_kernel](https://github.com/CodeLabClub/scratch3_python_kernel)
+ * [用户文档](/extension_guide/extension_python_kernel/)
+* [Tello 插件 (Node)](/dev_guide/最佳实践之tello插件/)
+ * 是一个 [Adapter Node](/dev_guide/Adapter-Node/)
+ * Adapter nodes: [node_tello3.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/nodes_v3/node_tello3.py)
+ * Scratch extension: [scratch3_tello3](https://github.com/CodeLabClub/scratch3_tello3)
+ * [用户文档](/extension_guide/tello3/)
+* thingDemo 插件 (Node)
+ * 是一个 [Adapter Node](/dev_guide/Adapter-Node/)
+ * Adapter nodes: [node_thingDemo.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/nodes_v3/node_thingDemo.py)
+ * Scratch extension: [scratch3_thingDemo](https://github.com/CodeLabClub/scratch3_thingDemo)
\ No newline at end of file
diff --git "a/docs/dev_guide/\346\234\200\344\275\263\345\256\236\350\267\265\344\271\213tello\346\217\222\344\273\266.md" "b/docs/dev_guide/\346\234\200\344\275\263\345\256\236\350\267\265\344\271\213tello\346\217\222\344\273\266.md"
new file mode 100644
index 0000000..d6d256a
--- /dev/null
+++ "b/docs/dev_guide/\346\234\200\344\275\263\345\256\236\350\267\265\344\271\213tello\346\217\222\344\273\266.md"
@@ -0,0 +1,365 @@
+
+# 更新提醒(2021-03-18)
+新的版本加入了与Scratch UI兼容的功能,相对于旧的版本要复杂些。
+
+# 前言
+
+开发一个 CodeLab Adapter 插件,往往会涉及两部分的工作:
+
+* 在 CodeLab Adapter 里写一个Python插件
+* 在 Scratch里写一个 js 插件
+
+类似前后端的配合,只是他们通过消息通信,而不是 REST API。
+
+本文侧重讨论Python插件部分。
+
+CodeLab Adapter 自带一个消息系统,理论上,任何语言都可以与之通信,任何有开放接口的事物都可以接入其中。
+
+本文仅讨论如何在 Scratch 上构建客户端(Scratch Extension,基于 JavaScript),使其与 CodeLab Adapter 通信(收发消息)来扩展 Scratch 的能力。当然,你也可以在任何语言中做这件事。
+
+
+
+# 思路
+
+一个 Adapter 插件(plugin)被视为 Adapter 系统的一个节点(Node), 通过这些节点去适配不同的外部硬件/软件,进而将其接入到系统中。
+
+
+
+在系统中,流动的一切都是消息,所以由这些插件连接的事物(软件/硬件)可以彼此沟通(talk),系统得以持续生长。
+
+希望使用 Adapter 某个插件的能力时(如在 Scratch 中),只需要发送消息与 Adapter 对话即可。
+
+# 开始
+
+## 案例(Tello)
+
+本文采用案例式教学。
+
+近期我们重写了 Adapter Tello 插件,本文将以此为例,介绍 **开发一个 CodeLab Adapter 插件** 的典型流程。
+
+该流程是完全通用的。
+
+### 如何交互?
+
+首先考虑第一个问题:你想接入什么东西?与之交互的方式是什么?(Adapter 是一个利用消息[不停交互的系统](/post/%E5%B0%91%E5%84%BF%E7%BC%96%E7%A8%8B/hardware-programming-style/))
+
+如果你想接入的东西是硬件(如 Tello),那么与之通信的方式可能是调用它们的开放 SDK 。
+
+如果你想接入的东西是软件(如 [Teachable Machine](https://adapter.codelab.club/extension_guide/teachable_machine/)),那么与之通信的方式可能是基于某些标准协议(如 http/websocket).
+
+如果你想接入的东西是一门编程语言的内核(如 Python),那么与之通信的方式可能是 `eval`
+
+#### 寻找 SDK
+
+在本文中,我们 **想接入什么东西** 是 Tello。 我们在 Github 上找到与之通信的 Python SDK: [DJITelloPy](https://github.com/damiafuentes/DJITelloPy)
+
+与 Tello 通信的方式是利用 socket, [DJITelloPy](https://github.com/damiafuentes/DJITelloPy)封装了细节,使我们可以以面向对象的风格与之交互, 我们来一撇 SDK 的使用方式。
+
+```python
+from djitellopy import Tello
+
+tello = Tello()
+
+tello.connect()
+tello.takeoff()
+
+tello.move_left(100)
+tello.rotate_counter_clockwise(90)
+tello.move_forward(100)
+
+tello.land()
+```
+
+语义清晰,非常简易。
+
+### 构建 Adapter 插件
+
+一个 Adapter 插件不是一个孤岛,它试图与其他事物交谈(talk), 对外部的请求做出回应。
+
+实现这件事的方式很多,软件工程有大量工作围绕这块: **对请求作出回应,提供服务**,我们会想到 RESTful API、RPC...
+
+Adapter 如果完成以上目标? 我们采取的策略是: **收发消息**。我们把一切看作消息, 并且倾向于晚绑定(late binding)
+
+回到正题。我们先来快速浏览一下 Tello 插件的代码(不必弄懂它,稍后会讲解)。
+
+```py
+import json
+import time
+
+from codelab_adapter_client import AdapterNode
+from codelab_adapter_client.thing import AdapterThing
+from djitellopy import Tello # https://github.com/wwj718/DJITelloPy/archive/master.zip
+from loguru import logger
+
+
+class TelloProxy(AdapterThing):
+ '''
+ 该类的主要职责是实现与 Scratch UI 的兼容性
+ '''
+ def __init__(self, node_instance):
+ super().__init__(thing_name="Tello", node_instance=node_instance)
+
+ def list(self, timeout=5) -> list:
+ # 必须实现
+ # scratch scan 会触发这个函数,返回值将进入 Scratch 扫描到的设备列表中。
+ if not self.thing:
+ self.thing = Tello()
+ self.thing.RESPONSE_TIMEOUT = timeout
+ logger.debug(f"self.thing: {self.thing}")
+ try:
+ self.thing.connect() # 返回True有问题,如果没有飞机,就会except
+ return ["192.168.10.1"]
+ except Exception as e:
+ logger.debug(f'error: {str(e)}')
+ self.node_instance.pub_notification(str(e), type="ERROR")
+ return []
+
+ def connect(self, ip, timeout=5):
+ # 必须实现
+ # 用户在 scratch 界面点击连接时,会触发该函数
+ if not self.thing:
+ self.thing = Tello()
+ is_connected = self.thing.connect() # 幂等操作 ,udp
+ self.is_connected = is_connected
+ return True
+
+ def status(self) -> bool:
+ # 必须实现
+ # return self.thing.connect()
+ pass
+
+ def disconnect(self):
+ # 必须实现
+ # Scratch 断开连接
+ self.is_connected = False
+ try:
+ if self.thing:
+ self.thing.clientSocket.close()
+ except Exception:
+ pass
+ self.thing = None
+
+
+class Tello3Node(AdapterNode):
+ NODE_ID = "eim/node_tello3"
+ HELP_URL = "https://adapter.codelab.club/extension_guide/tello3/"
+ DESCRIPTION = "tello 3.0" # list connect
+ VERSION = "3.0.0"
+
+ def __init__(self, **kwargs):
+ super().__init__(logger=logger, **kwargs)
+ self.tello = TelloProxy(self)
+
+ def run_python_code(self, code):
+ '''
+ 此处定义了与外部系统(诸如Scratch)沟通的有效消息
+ list: Scratch 发现设备
+ connect: scratch 建立连接
+ disconnect: scratch 断开连接
+ tello: 可调用的对象,一般被scratch具体功能积木调用,消息是传递面向对象风格的Python代码,如 tello.takeoff()
+ '''
+ try:
+ output = eval(
+ code,
+ {"__builtins__": None},
+ {
+ "tello": self.tello.thing,
+ "connect": self.tello.connect,
+ "disconnect": self.tello.disconnect,
+ "list": self.tello.list,
+ })
+ except Exception as e:
+ output = e
+ return output
+
+ def extension_message_handle(self, topic, payload):
+ # 必须实现
+ # 与当前插件有关的消息都流入该函数
+ self.logger.info(f'code: {payload["content"]}')
+ python_code = payload["content"]
+ output = self.run_python_code(python_code)
+ try:
+ output = json.dumps(output)
+ except Exception:
+ output = str(output)
+ payload["content"] = output
+ message = {"payload": payload}
+ self.publish(message)
+
+ def run(self):
+ # 用于block进程,当收到进程停止消息(将切换self._running状态),则结束阻塞
+ while self._running:
+ time.sleep(0.5)
+
+ def terminate(self, **kwargs):
+ # 必须实现
+ # 插件退出钩子,可以执行所需的资源清理(诸如释放设备)
+ try:
+ self.tello.disconnect()
+ except Exception:
+ pass
+ super().terminate(**kwargs)
+
+
+def main(**kwargs):
+ # 入口函数,启动插件时将以独立 Python 进程运行。
+ try:
+ node = Tello3Node(**kwargs)
+ node.receive_loop_as_thread()
+ node.run()
+ except Exception as e:
+ if node._running:
+ node.pub_notification(str(e), type="ERROR")
+ time.sleep(0.1)
+ node.terminate()
+
+
+if __name__ == "__main__":
+ main()
+```
+
+你可以使用 [Adapter 内置的 JupyterLab](https://adapter.codelab.club/extension_guide/jupyterlab/) 浏览/修改 这些插件源码, 保存并重启插件之后,即刻生效(不需要重启 Adapter)
+
+
+
+我们来看看 tello 插件各部分代码的含义和功能是什么(主要关心 Tello3Node)
+
+
+
+
+可是,并没有见到跟 Tello 有关的业务逻辑啊?
+
+是的,这正是我们想法的核心部分: `晚绑定(late binding)`, 将功能描述不断后推,交给 client(甚至是用户)。
+
+Adapter Tello 插件看起来颇像一个 REPL,它解释(run_python_code)收到的消息(副作用是 tello 飞行器的行为), Tello 的行为将由输入的消息决定,消息携带语义。我们贪图便利,直接将 Python 代码视为消息(因其能很好携带语义)
+
+### 客户端
+
+接下来我们来构建一个客户端来使用 Adapter Tello 插件。
+
+前头提到,我们计划在 Scratch 里构建一个客户端,它是一个 Scratch Extension。
+
+#### Scratch Extension
+
+如果你对构建 Scratch Extension 不熟悉,请参考: [创建你的第一个 Scratch3.0 Extension](/post/%E5%B0%91%E5%84%BF%E7%BC%96%E7%A8%8B/create-first-scratch3-extension/)
+
+我们已经将 Scratch Tello 插件开放在这儿: [scratch3_tello3](https://github.com/CodeLabClub/scratch3_tello3)
+
+##### 如何交互(talk)?
+
+前头提到:
+
+> 一个 Adapter 插件不是一个孤岛,它试图与其他事物交谈(talk), 对外部的请求做出回应。
+
+Scratch Tello 插件(JavaScript)是如何与 Adapter 插件(Python)交互的呢?
+
+它们通过 websocket(socketio)沟通, 但你不需要在意和弄懂它们沟通的细节,我们已经构建了一个 [Adapter js client](https://github.com/CodeLabClub/scratch3_eim/blob/v3/codelab_adapter_base.js),抽象掉了 talk 的细节,让你可以基于它轻松在 js 里与 Adapter 交互。 (注意:你的开发环境里,需要有[scratch3_eim](https://github.com/CodeLabClub/scratch3_eim))
+
+##### 源码解读
+
+接下来,一起深入到源码里看看。
+
+我们通过阐述这两块积木,来看看引擎盖后发生的事情。
+
+
+
+首先看看,当我们 **起飞** 积木运行的时候发生了什么:
+
+
+
+实际上,当 Scratch 中, **起飞** 积木运行时,消息 `tello.takeoff()` 将发送到 Adapter Tello 插件,插件将解释这则消息-- eval(执行)这段 Python 代码。
+
+接着我们来看看 **设置速度** 积木(带有参数)运行的时候发生了什么:
+
+
+
+可以看出,我们试图将参数拼凑到 Python 代码里。
+
+`this.client.emit_with_messageid` 是与 Adapter 通信的关键,这部分也很简单,只是发送消息,如果你兴趣不大,不需要弄懂它, 将其视为模版代码,跟着既有的插件(我们开放了插件)填空即可。
+
+需要注意的是,发往插件的消息并不一定是 Python 代码,它只要携带语义就行。
+
+[AdapterBaseClient](https://github.com/CodeLabClub/scratch3_tello3/blob/main/index.js#L13) 类是与 Adapter 通信的唯一入口。 AdapterBaseClient在[初始化的时候](https://github.com/CodeLabClub/scratch3_tello3/blob/main/index.js#L152), 允许传入一些回调函数,获取来自Adapter一侧的消息。
+
+
+## 发布 Adapter 插件
+
+如果你构建了新的 Adapter 插件,欢迎提交到[插件市场](https://adapter.codelab.club/extension_guide/extension_market/)
+
+# 调试
+
+为了方便开发 Adapter 插件,一些[调试技巧](https://adapter.codelab.club/dev_guide/debug/)可能对你有用
+
+# 进阶 && 进一步阅读
+
+- [scratch3_python_kernel](https://github.com/CodeLabClub/scratch3_python_kernel)
+- [scratch3_usb_microbit](https://github.com/CodeLabClub/scratch3_usb_microbit)
+- [scratch3_microbit_radio](https://github.com/CodeLabClub/scratch3_microbit_radio)
+- [Python 对象的连接器:EIM 插件](https://adapter.codelab.club/project_tutorial/eim_pt/)
+- [scratch3_cozmo](https://github.com/CodeLabClub/scratch3_cozmo)
+- [Scratch 拓展最佳实践 -- 以 Cozmo 为例](https://codelab.club/blog/2020/04/26/%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/)
+
+# FAQ
+## 我手头没有 Tello,怎么更方便调试
+你可以自定义一个一个Tello类替代 `from djitellopy import Tello`, 只需要实现js积木里调用的方法即可,诸如 `takeoff`, 这种方式有助于你理解沟通过程。
+
+当然,你也可以使用我们构建了一个模拟设备的例子:
+
+* Adapter nodes: [node_thingDemo.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/nodes_v3/node_thingDemo.py)
+* Scratch extension: [scratch3_thingDemo](https://github.com/CodeLabClub/scratch3_thingDemo)
+
+
+
+## 如何刷入自定义固件
+
+- [from codelab_adapter.utils import list_microbit, flash_usb_microbit, flash_makecode_file](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/extensions_v3/extension_usb_microbit.py#L7)
+
+## Adatper 内置了哪些第三方库
+
+[wiki](https://github.com/CodeLabClub/codelab_adapter_extensions/wiki)
+
+## 如何引入新的 Python 第三方库
+
+Adapter 允许[再分发](https://adapter.codelab.club/dev_guide/%E5%AE%9A%E5%88%B6%E4%B8%8E%E5%88%86%E5%8F%91/), 把需要的第三方库放在相应目录下,再分发即可
+
+放在目录下即可,再分发
+
+更多 [FAQ](https://adapter.codelab.club/dev_guide/FAQ/)
+
+# 参考
+
+- [创建你的第一个 Scratch3.0 Extension](/post/%E5%B0%91%E5%84%BF%E7%BC%96%E7%A8%8B/create-first-scratch3-extension/)
+- [scratch3_tello2](https://github.com/CodeLabClub/scratch3_tello2)
+- [scratch3_eim](https://github.com/codelabclub/scratch3_eim)
+- [scratch3_python_kernel](https://github.com/CodeLabClub/scratch3_python_kernel)
+- [scratch3_usb_microbit](https://github.com/CodeLabClub/scratch3_usb_microbit)
+- [scratch3_microbit_radio](https://github.com/CodeLabClub/scratch3_microbit_radio)
+- [Python 对象的连接器:EIM 插件](https://adapter.codelab.club/project_tutorial/eim_pt/)
+- [scratch3_cozmo](https://github.com/CodeLabClub/scratch3_cozmo)
+- [Scratch 拓展最佳实践 -- 以 Cozmo 为例](https://codelab.club/blog/2020/04/26/%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/)
+- [jsonrpc.js](https://github.com/LLK/scratch-vm/blob/acc2e6dba2e5a32668f0b26f0b2c4dfdecbe1023/src/util/jsonrpc.js)
+- [codelab_adapter_base.js](https://github.com/CodeLabClub/scratch3_eim/blob/v3/codelab_adapter_base.js#L291)
+- [建立在异步消息之上的同步指令](/post/%E7%BC%96%E7%A8%8B/async-msg-sync-cmd/)
+- [两种硬件编程风格的比较](/post/%E5%B0%91%E5%84%BF%E7%BC%96%E7%A8%8B/hardware-programming-style/)
diff --git a/docs/extension_guide/AdapterThing.md b/docs/extension_guide/AdapterThing.md
new file mode 100644
index 0000000..653320e
--- /dev/null
+++ b/docs/extension_guide/AdapterThing.md
@@ -0,0 +1,18 @@
+# Adapter Thing
+
+Adapter Thing 目前被视为在 Adapter 里接入设备的最佳实践。
+
+它是一套接口规范,便于与对 外部 Client 提供服务,Scratch Extension 是一种 client。
+
+# thingDemo
+* [node_thingDemo.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/nodes_v3/node_thingDemo.py)
+* [scratch3_thingDemo](https://github.com/CodeLabClub/scratch3_thingDemo)
+
+# Demo
+以下是一些基于 Adapter Thing 接口接入的真实设备, 大家可以自行翻阅源码
+
+* [Tello3](https://adapter.codelab.club/extension_guide/tello3/): 推荐
+* [RoboMaster2](https://adapter.codelab.club/extension_guide/RoboMaster2/)
+* BrainCo
+* Lego Mario
+* Sphero RVR
\ No newline at end of file
diff --git a/docs/extension_guide/Aqara.md b/docs/extension_guide/Aqara.md
index d67e3d4..7520886 100644
--- a/docs/extension_guide/Aqara.md
+++ b/docs/extension_guide/Aqara.md
@@ -2,6 +2,9 @@

+!!!提醒
+ Aqara 是实验性插件,未来可能移除。如果你比较依赖于它,建议自行维护Adapter Node,直接与Aqara云通信,这是一个例子: [extension_Aqara_scene.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/extensions_v3/extension_Aqara_scene.py)。 我们准备长期支持的项目是 Longan hub 、 Home Assistant 和 WebThings。
+
## 介绍
Aqara(绿米)智能家居用户可在 CodeLab 创作平台上对智能设备进行编程,让孩子将智能家庭改造为魔法世界吧!
@@ -10,6 +13,8 @@ Aqara(绿米)智能家居用户可在 CodeLab 创作平台上对智能设备进
+更多好玩的演示 参考: [CodeLab projects](https://www.codelab.club/projects/)
+
# 使用
!!! 提醒
目前只支持 Aqara 网关,不支持小米网关。
@@ -29,4 +34,4 @@ Aqara(绿米)智能家居用户可在 CodeLab 创作平台上对智能设备进

# 扩展性
-使用 `场景` 积木,可以调用 Aqara APP 里定义的任何场景! 如果你发现某些设备没有被积木化,可以通过把它纳入场景中,之后通过场景积木调用它!
\ No newline at end of file
+使用 `场景` 积木,可以调用 Aqara APP 里定义的任何场景! 如果你发现某些设备没有被积木化,可以通过把它纳入场景中,之后通过场景积木调用它!
diff --git a/docs/extension_guide/Box2D.md b/docs/extension_guide/Box2D.md
index d1120b7..7555c70 100644
--- a/docs/extension_guide/Box2D.md
+++ b/docs/extension_guide/Box2D.md
@@ -1,80 +1,51 @@
-# Tutorial
-
-该文档由 [@Hanson 同学](http://www.concentric-circle.com/author/admin/) 创建。
-
+# 物理引擎
## 介绍
+物理引擎扩展是基于 [Box2D](https://box2d.org/) 开发的用于模拟物理规则的游戏引擎,使用它可以很方便地模拟重力、物体间的碰撞,制作游戏变得更加简单。先体验[吃货大冒险](https://create.codelab.club/projects/10726/)项目来看看物理引擎的效果吧。
+
-Box2D 是一个被广泛使用的 2D C++物理引擎,Codelab Scratch 中的 Box2D 插件基于 Javascript 构建。
-
-不依赖于 CodeLab Adapter。
-
-# 步骤 1 在 Codelab Scratch 中导入 Box2D 插件
-
-
-
-# 步骤 2 创建一个 Box2D 世界
-
-创建一个世界之后才能模拟物理。
-
-
-
-使用 setup stage 模块在舞台上创建一个世界。它有 3 种样式:
-
-1. Boxed stage:有地板、墙和天花板的世界。墙和地板分别是舞台的下、左右边框,而天花板在舞台的 y=940 的高度。
-2. Open(with floor):只有地板的世界。
-3. Open(no floor):啥也没有的世界,但它还是一个世界。
-
-之后可以选择设置重力中心(set gravity to),可以想象那里有一个质量无限大的物体吸引着其他物体。也可以不设置,默认在舞台下方。
-
-# 步骤 3 使角色加入到物理世界
-
-只有被加入到物理世界他才会被物理法则所影响。
-
-
-
-模块:Enable for**_mode_**
-
-参数 1 影响的对象:
-
-1. This costume 这个角色
-2. This circle 角色的外接圆
-3. This polygon 并不知道是什么意思,因为不会使用
-4. All sprites 所有角色
-
-参数 2 模式:
-
-1. Normal 普通
-2. Precision 精确
-
-# 步骤 4 重复模拟
-
-使用一个循环,其中有模块 step simulate 来重复模拟。
-
-完成!
+## Hello World
+接下来我们使用物理引擎扩展制作一个角色能够跳跃和移动的项目。
-至此,最基础的架构世界已经完成了。你现在可以使用运动模块等来让角色动起来。这些模块很好理解,只需要在架构好的世界里尝试一次就知道了。
+### 1. 加载物理引擎扩展
-注意:你的角色的质量会因大小而改变。你可以设置密度来影响它。
+从扩展库中选择“物理引擎”扩展,也可以直接通过搜索框搜索扩展。
+
-其它模块解释:
+### 2. 让角色跳起来
-- Push with force**_in direction_** 向某个方向施加一定大小的力。有惯性的影响。
- 注意:你的角色的质量会因大小而改变。你可以设置密度来影响它。
+拼接下图中的积木,当按下上键,小猫竖直方向上的速度会变成 15,小猫向上移动,由于物理引擎会模拟重力效果,小猫最终会落回地面,这样就制作了跳跃的功能。在开始的时候需要设置角色的形状,如果想让物理引擎起作用,“逐步模拟”积木需要一直运行。
+
-- Spin with force \_\_\_ 顺时针旋转角色。参数为力量大小,有方向。同样受到惯性的影响。
+### 3. 让角色移动
-- Set density**_ roughness_** 设置密度和光滑程度。不知为何这里采用的是形容词来确定五种程度。
+现在,小猫可以跳跃了,添加以下积木就可以让小猫左右移动,同时按上键和右键,小猫会向前方跳跃。
+
+添加一个新角色 Paddle,设置它的形状并且让它固定住,现在它也拥有了物理属性,小猫可以跳到平板上。注意,小猫角色已经添加了“逐步模拟”积木,平板角色不需要重复添加。完整程序见[【物理引擎】跳跃和移动](https://create.codelab.club/projects/12707/editor/)项目。
+
-- Set fixed\_\_\_ 设置固定。
+## 积木说明
+物理引擎积木的介绍请参考项目[【物理引擎】积木介绍](https://create.codelab.club/projects/13039/editor/),里面有逐个积木的介绍和相应的示例。
+
-- Touching any/feet 碰撞判断,参数为整个身体/脚。
+## 拓展
+### 更多项目
+[CodeLab 社区](https://create.codelab.club/explore/projects/all?extension=griffpatch)有很多物理引擎相关的项目,这里列出部分项目:
-- Scroll 指的是舞台的滚动,舞台、重力中心和所有角色都会滚动。
+- [【物理引擎】教程](https://create.codelab.club/studios/435)
+- [鼠标的吸引力3](https://create.codelab.club/projects/11212/)
+- [Floating Bubbles remix remix](https://create.codelab.club/projects/11058/)
+- [弹射: 小猫吃西瓜 v0.9](https://create.codelab.club/projects/10032/)
+- [桌球](https://create.codelab.club/projects/11429/)
+- [合成大西瓜1.2](https://create.codelab.club/projects/9151/)
+- [[Box2D] 风火轮](https://create.codelab.club/projects/9985/)
-# 异常
+### FAQ
+**1. 物理引擎不起作用**
-当你的程序中出现了问题时,它并不会崩溃。现象就是你无法点击小绿旗来启动这个程序。此时你需要做的是检查程序,或是试着移除部分程序以确定哪里出现了问题。
+可能是以下原因:
-# 已知问题
+- “逐步模拟”积木没有一直运行
+- 没有设置角色的形状
+- 角色隐藏起来了
-当使角色加入物理世界时模块 Enable for**_mode_**参数为 this polygon 时会出现未知问题
+如果有更多物理引擎相关的问题或想法,欢迎在 [CodeLab 论坛](https://discuss.codelab.club/)交流讨论。
\ No newline at end of file
diff --git a/docs/extension_guide/Calypso.md b/docs/extension_guide/Calypso.md
index 65e9a6f..2a5a1a5 100644
--- a/docs/extension_guide/Calypso.md
+++ b/docs/extension_guide/Calypso.md
@@ -10,7 +10,7 @@
# 插件说明
- 使用方式: 到[插件市场](/extension_guide/extension_market/)下载插件, 搜索 **mqtt**
-- 插件类型: [Adapter Extension](https://adapter.codelab.club/dev_guide/helloworld/)
+- 插件类型: [Adapter Extension](/dev_guide/helloworld/)
- 插件源码: [extension_calypso.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/extensions_v3/extension_calypso.py)
-->
diff --git a/docs/extension_guide/EP3.md b/docs/extension_guide/EP3.md
new file mode 100644
index 0000000..9d24c0e
--- /dev/null
+++ b/docs/extension_guide/EP3.md
@@ -0,0 +1,75 @@
+# RoboMaster 3.0
+
+!!!提醒
+ 目前只支持RoboMaster EP(暂不支持S1),受限于大疆的开放接口: [RoboMaster SDK 新手入门 - EP 篇](https://robomaster-dev.readthedocs.io/zh_CN/latest/python_sdk/beginner_ep.html)
+
+RoboMaster EP 插件的 3.0 版本, 基于 DJI 官方的 SDK: [RoboMaster SDK](https://robomaster-dev.readthedocs.io/zh_CN/latest/python_sdk/beginner_drone.html)库。
+
+能够充分利用设备的能力。
+
+该插件能做到阻塞式(`wait_for_completed`)运行,这对于执行 **序列** 类程序很棒
+
+# 使用说明
+目前该插件并未内置到 Adapter 中(因其复杂的打包依赖,而且跨平台兼容性不好)。
+
+我们目前将插件构建为 [Adapter Node](/dev_guide/Adapter-Node/),可以在Adapter外部以普通Python文件运行,一旦运行起来,与普通Adapter插件是一样的,能够与Adapter体系的所有事物交互。
+
+## Python环境
+首先你本地需要有 Python 环境(`Python>=3.6`)
+
+你可以到 [Python 官方](https://www.python.org/)下载,也可以使用 CodeLab放在[国内的版本(Python3.7)](https://www.codelab.club/blog/2020/08/20/tools#python)
+
+!!! 提醒
+ Mac 用户和 Linux 本地很可能内置了 Python3
+
+### 安装依赖
+```bash
+pip install robomaster codelab_adapter_client --upgrade
+```
+
+## 开始!
+
+!!! 提醒
+ Tello 会占用 wifi,导致电脑无法联网,请使用 CodeLab Adapter 的离线模式: [FAQ:离线使用](/user_guide/FAQ/#_6) (在`>=3.4.0`的版本中可用)。
+
+
+### 步骤 1:打开 [CodeLab Scratch](https://scratch-beta.codelab.club)
+运行CodeLab Adapter, 确保在线平台与Adapte连接正常。
+
+看到 [CodeLab Scratch](https://scratch-beta.codelab.club) 指示灯显示绿色,代表连接成功。
+
+
+
+
+### 步骤 1:运行 [node_RoboMaster3.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/nodes_v3/node_RoboMaster3.py)
+
+将 [node_RoboMaster3.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/nodes_v3/node_RoboMaster3.py) 插件下载到本地(随便放在一个文件夹里),在命令行中进入到这个文件夹,使用 `python node_RoboMaster3.py` 运行它。
+
+### 步骤 2: 前进!
+
+选择 scratch3 中的 EIM 插件.
+
+
+
+
+
+以下是一个简单 demo:
+
+
+
+
+[EP3-demo](https://scratch-beta.codelab.club/?sb3url=https://adapter.codelab.club/sb3/ep3-demo.sb3)
+
+
+
+
+
+
+
+# 进阶
+
+更多API参考文档: [RoboMaster SDK](https://robomaster-dev.readthedocs.io/zh_CN/latest/python_sdk/beginner_drone.html)
\ No newline at end of file
diff --git a/docs/extension_guide/EasyOCR.md b/docs/extension_guide/EasyOCR.md
index 85dc8b3..5103c53 100644
--- a/docs/extension_guide/EasyOCR.md
+++ b/docs/extension_guide/EasyOCR.md
@@ -11,7 +11,8 @@

-使用 pip 安装 easyocr: `python -m pip install easyocr codelab_adapter_client>=1.9.2`
+在 Adapter [Jupyterlab 安装第三方库](https://adapter.codelab.club/extension_guide/jupyterlab/#_4) easyocr: `import pip; pip.main(['install', 'easyocr'])`
+
运行以下程序:
@@ -30,7 +31,7 @@ def monitor(msg):
run_monitor(monitor)
```
-对以上机制不熟悉的朋友可以参考 [使用4 行 Pyhon 代码扩展 Scratch](https://blog.just4fun.site/post/%E5%B0%91%E5%84%BF%E7%BC%96%E7%A8%8B/4-line-python-code-as-scratch-ext/)
+对以上机制不熟悉的朋友可以参考 [Python对象的连接器:EIM 插件](/project_tutorial/eim_pt/)
打开 [测试项目](https://scratch-beta.codelab.club/?sb3url=https://adapter.codelab.club/sb3/Scratch-EasyOCR.sb3)
diff --git a/docs/extension_guide/GameShell.md b/docs/extension_guide/GameShell.md
index a8be4e4..7f329ee 100644
--- a/docs/extension_guide/GameShell.md
+++ b/docs/extension_guide/GameShell.md
@@ -1,5 +1,5 @@
# Tutorial
-
+
## 依赖
@@ -7,12 +7,12 @@
{!utils/dependence.md!}
## 使用
-参考[作为游戏机厅的 CodeLab 可编程空间](https://blog.just4fun.site/post/codelab-gameshell/)
+参考[作为游戏机厅的 CodeLab 可编程空间](https://wwj718.github.io/post/codelab-gameshell/)
# Demo
将 GameShell 接入 CodeLab Adapter 之后, 我们将其用作 CodeLab 可编程空间的控制手柄:
-
+
-
+
diff --git a/docs/extension_guide/HCI.md b/docs/extension_guide/HCI.md
index 3e3746f..923a955 100644
--- a/docs/extension_guide/HCI.md
+++ b/docs/extension_guide/HCI.md
@@ -4,15 +4,18 @@
{!utils/dependence.md!}
+## 步骤 1:安装依赖
+Windows 和 Mac 用户开箱可用。 Linux需要安装依赖:
-
-## 步骤 1:安装依赖([PyAutoGUI](https://pyautogui.readthedocs.io/en/latest/index.html))
参考 [PyAutoGUI Install](https://pyautogui.readthedocs.io/en/latest/install.html)
`pip3 install codelab_adapter_client --upgrade`
ps: 使用 Python3
+!!! 提醒
+ MacOS 升级到最新版本后,可能会导致部分控制类积木无法使用,使系统安全机制升级造成的(我也是可悲的 Mac 用户,下个计算机一定要使用开源系统。)。详情参考:[Pyautogui doesn't seem to work on macOS Mojave](https://github.com/asweigart/pyautogui/issues/247)。 相关问题: [Catalina does not allow to capture the screen](https://github.com/BoboTiG/python-mss/issues/134)。这个问题的结局方案似乎是让系统信任Adapter内置的Python: `codelab-adapter-3_7_3-mac.app/Contents/Resources/Support/bin/python3`
+
## 步骤 2:打开 Codelab Adapter
{!utils/open_adapter.md!}
@@ -29,6 +32,45 @@ ps: 使用 Python3
选择对应的 Scratch3 插件:HCI
+# demo
+尚雅学校目前在使用 CodeLab Adapter,有位老师想使用魔杖激活开场视频,使用 HCI 插件可以轻松做到:
+
+
+
+以下是源码:
+
+[魔杖播放视频](https://create.codelab.club/projects/8499/)
+
+
+
+### Windows 10 用户
+Windows10的某些版本,默认会在页面下方闪烁播放器图标,而不是打开。
+
+
+
+可以考虑使用网页打开视频链接,如果你的视频在本地,可以托管给 Adapter,具体而言:
+
+* 在 Adapter Web UI 里 **打开扩展目录**
+
+
+
+* 将视频放到 **扩展目录** 下的 **src** 目录里
+
+
+
+* 现在这个视频可以使用链接打开了! 它的地址为: `https://codelab-adapter.codelab.club:12358/static/1608804837247213.mp4`, 注意,最后的 **1608804837247213.mp4** 是你的视频名字,最好不用中文。
+
+这是一个示例程序: [魔杖开场视频--使用链接](https://create.codelab.club/projects/8544/editor){target=\_blank}
+
## 高阶用法
HCI 插件允许你写 Python 代码,[PyAutoGUI](https://pyautogui.readthedocs.io/en/latest/index.html) 文档中的所有功能你都可以在 Scratch 中使用。
diff --git a/docs/extension_guide/Kano_Wand.md b/docs/extension_guide/Kano_Wand.md
index 95d544b..36824b8 100644
--- a/docs/extension_guide/Kano_Wand.md
+++ b/docs/extension_guide/Kano_Wand.md
@@ -13,7 +13,8 @@ Hack [Kano Code](https://kano.me/us/landing/app),使其与 CodeLab Adapter 兼
目前我对 Mac 和 Windows 的[Kano Code](https://kano.me/us/landing/app)做了简单 hack,使其能够接入 Adapter,由于是hack过的软件,不便于在互联网分发,如果你需要,请联系我们。
!!! tips
- mac系统新版本(13.14之后)安全性提高,如果无法运行hack后的软件(Mac应用已损坏,打不开),如果可能需要先运行: `sudo spctl --master-disable` 或者 `sudo xattr -rd com.apple.quarantine 空格 软件的路径`
+ mac系统新版本(13.14之后)安全性提高,如果无法运行hack后的软件(Mac应用已损坏,打不开),如果可能需要先运行: `sudo spctl --master-disable` 或者 `sudo xattr -rd com.apple.quarantine 空格 软件的路径`。
+ Kano Wand App 发布了新版本,修复了 Mac 下闪退的问题,目前我们也做了兼容。
## 步骤 2:打开 与 Adapter 兼容的 Kano Code
!!! tips
@@ -54,3 +55,11 @@ Hack [Kano Code](https://kano.me/us/landing/app),使其与 CodeLab Adapter 兼
## Demo
https://adapter.codelab.club/user_guide/gallery/#kano-wand
+
+
+
\ No newline at end of file
diff --git a/docs/extension_guide/MQTT_Broker.md b/docs/extension_guide/MQTT_Broker.md
index 3229381..ed81e77 100644
--- a/docs/extension_guide/MQTT_Broker.md
+++ b/docs/extension_guide/MQTT_Broker.md
@@ -7,7 +7,7 @@
# 插件说明
- 使用方式: 到[插件市场](/extension_guide/extension_market/)下载插件, 搜索 **mqtt**
-- 插件类型: [Adapter Extension](https://adapter.codelab.club/dev_guide/helloworld/)
+- 插件类型: [Adapter Extension](/dev_guide/helloworld/)
- 插件源码: [extension_mqtt_broker.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/extensions_v3/extension_mqtt_broker.py)
# 使用场景
diff --git a/docs/extension_guide/MQTT_adapter.md b/docs/extension_guide/MQTT_adapter.md
index f66f01b..f556b32 100644
--- a/docs/extension_guide/MQTT_adapter.md
+++ b/docs/extension_guide/MQTT_adapter.md
@@ -3,7 +3,7 @@
# 插件说明
- 使用方式: 到[插件市场](/extension_guide/extension_market/)下载插件, 搜索 **mqtt**
-- 插件类型: [Adapter Extension](https://adapter.codelab.club/dev_guide/helloworld/)
+- 插件类型: [Adapter Extension](/dev_guide/helloworld/)
- 插件源码: [extension_mqtt_adapter.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/extensions_v3/extension_mqtt_adapter.py)
# 使用场景
@@ -14,7 +14,7 @@ extension_mqtt_adapter.py 插件桥接 mqtt 与 Scratch。
## mqtt -> Scratch
!!! 提醒
- 你需要首先选择一个mqtt broker,[extension_mqtt_adapter.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/extensions_v3/extension_mqtt_adapter.py#L24)假设你在本地运行了一个mqtt broker(mqtt 127.0.0.1 1883)。你可以使用任何mqtt broker。在 Adapter 3.2 中,将自带一个轻量级高性能的 mqtt broker: [MQTT Broker](https://adapter.codelab.club/extension_guide/MQTT_Broker/)
+ 你需要首先选择一个mqtt broker,[extension_mqtt_adapter.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/extensions_v3/extension_mqtt_adapter.py#L24)假设你在本地运行了一个mqtt broker(mqtt 127.0.0.1 1883)。你可以使用任何mqtt broker。在 Adapter 3.2 中,将自带一个轻量级高性能的 mqtt broker: [MQTT Broker](/extension_guide/MQTT_Broker/)
将消息从mqtt client 发往Scratch:
diff --git a/docs/extension_guide/NetworkZero.md b/docs/extension_guide/NetworkZero.md
index e9c4149..917728e 100644
--- a/docs/extension_guide/NetworkZero.md
+++ b/docs/extension_guide/NetworkZero.md
@@ -19,9 +19,20 @@ NetworkZero 的目标是让局域网通信变得简单。
允许任何人公布通信地址,所以可以轻松构建网状结构。
+# demo2: 悟空机器人
+[demo2](https://scratch-beta.codelab.club/?sb3url=https://adapter.codelab.club/sb3/networkzero-demo.sb3)
+
# 典型应用场景
* 局域网联机游戏
* 局域网聊天
* 多人联机控制机器人
* 与Python程序(诸如树莓派机器人)互动
- * 可与 基于[networkzero](https://github.com/tjguk/networkzero/)的程序互操作。
\ No newline at end of file
+ * 可与 基于[networkzero](https://github.com/tjguk/networkzero/)的程序互操作。
+
+```py
+OSError: [WinError 10040] 一个在数据报套接字上发送的消息大于内部消息缓冲区或其他一些网络限制,或该用户用于接收数据报的缓冲区比数据报小。
+ File "networkzero\discovery.py", line 405, in run
+ self.listen_for_one_advert()
+ File "networkzero\discovery.py", line 289, in listen_for_one_advert
+ message, source = self.socket.recvfrom(self.beacon_message_size)
+```
\ No newline at end of file
diff --git a/docs/extension_guide/ROS.md b/docs/extension_guide/ROS.md
index 81747bd..c41f6fb 100644
--- a/docs/extension_guide/ROS.md
+++ b/docs/extension_guide/ROS.md
@@ -1,6 +1,6 @@
# ROS
### 步骤 1:打开 CodeLab Scratch
-下载 [CodeLab Scratch Desktop(离线版)](https://www.codelab.club/blog/codelab-download/),并运行它。
+下载 [CodeLab Scratch Desktop(离线版)](https://www.codelab.club/blog/2020/08/20/tools),并运行它。

diff --git a/docs/extension_guide/RoboMaster.md b/docs/extension_guide/RoboMaster.md
index 46af1b6..d306b4d 100644
--- a/docs/extension_guide/RoboMaster.md
+++ b/docs/extension_guide/RoboMaster.md
@@ -1,8 +1,10 @@
# RoboMaster
RoboMaster EP
-!!! 提醒
- RoboMaster EP 最近SDK做了更新,建议到插件市场下载最新插件。
+!!!提醒
+ 目前只支持RoboMaster EP(暂不支持S1),受限于大疆的开放接口: [RoboMaster SDK 新手入门 - EP 篇](https://robomaster-dev.readthedocs.io/zh_CN/latest/python_sdk/beginner_ep.html)
+
+
# Tutorial
## 依赖
@@ -33,3 +35,37 @@ RoboMaster EP
## 更多
协议细节参考[ robomaster-dev 协议内容](https://robomaster-dev.readthedocs.io/zh_CN/latest/sdk/protocol_api.html)
+
+!!! 提醒
+ 如果你希望做一些更复杂的事,建议直接使用社区里的 Python SDK与 设备交互,之后使用 [Adapter Node](/dev_guide/Adapter-Node/) 将其接入Adapter环境中。
+
+如果你要做复杂任务,建议在Python中与Robomaster交互:
+
+* [robomasterpy](https://github.com/nanmu42/robomasterpy)
+* [RoboMaster-SDK](https://github.com/dji-sdk/RoboMaster-SDK): 官方SDK跨平台性非常差, 似乎是因为比较早期的缘故
+
+## 有些网络无法连接到robomaster
+[如何排查 无法发现设备 的问题?](https://adapter.codelab.club/user_guide/FAQ/#_9)
+
+## 控制 led
+- [led_control](https://robomasterpy.nanmu.me/en/latest/api.html#robomasterpy.Commander.led_control)
+ - [comp](https://github.com/nanmu42/robomasterpy/blob/e38103621cdee9503226178cdd5e65a461607198/robomasterpy/client.py#L47)
+ - [effect](https://github.com/nanmu42/robomasterpy/blob/e38103621cdee9503226178cdd5e65a461607198/robomasterpy/client.py#L61)
+
+
+
\ No newline at end of file
diff --git a/docs/extension_guide/RoboMasterEP2.md b/docs/extension_guide/RoboMasterEP2.md
new file mode 100644
index 0000000..62b581a
--- /dev/null
+++ b/docs/extension_guide/RoboMasterEP2.md
@@ -0,0 +1,7 @@
+# RoboMaster 2.0
+
+支持在 scratch 中扫描连接。
+
+功能同 [RoboMaster](/extension_guide/RoboMaster/)
+
+使用 [robomasterpy](https://github.com/nanmu42/robomasterpy) 驱动。
diff --git a/docs/extension_guide/SimplePyboard.md b/docs/extension_guide/SimplePyboard.md
index 21f0965..3bc6b65 100644
--- a/docs/extension_guide/SimplePyboard.md
+++ b/docs/extension_guide/SimplePyboard.md
@@ -2,6 +2,6 @@

-支持大多数的 pyboard (烧录 micropython 的 board), 诸如 esp32、esp8266、micro:bit、 掌控板、bpi:bit...
+支持大多数的 pyboard (烧录 [micropython](http://micropython.org/download/) 的 board), 诸如 esp32、esp8266、[micro:bit](https://microbit-micropython.readthedocs.io/)、 掌控板、bpi:bit...
采用的是 micropyhton 的 REPL 机制(串口)
diff --git a/docs/extension_guide/Squeak.md b/docs/extension_guide/Squeak.md
new file mode 100644
index 0000000..e0aca61
--- /dev/null
+++ b/docs/extension_guide/Squeak.md
@@ -0,0 +1,47 @@
+# Squeak/Smalltalk
+
+## 介绍
+
+
+
+[Squeak](http://squeak.org/) 是 Smalltalk 的现代实现.
+
+有几位 Smalltalk-80 的实现者(他们之前在施乐实验室创造了 Smalltalk)都参与到了 Squeak 中,包括 Alan Kay 和 Daniel Ingall,这个项目依然在持续演进,他们抱有跟今天计算机整个领域不同的愿景。
+
+MIT媒体实验室推动的OLPC计划,采用Squeak作为开发环境。
+
+Etoys、Croquet、第一代的Scratch都是用 Squeak 实现的。
+
+关于 Squeak 入门,可参考[Smalltalk 入门导览](https://wwj718.github.io/post/%E7%BC%96%E7%A8%8B/smalltalk-guide/)
+
+## 接入 Adapter
+有很多种方式将 Squeak 和 Adapter连在一起,诸如HTTP、Websocket、ZeroMQ,系统调用(Adapter python client提供系统命令),你也可以自己写一个Adapter插件来连接两者。
+
+目前我最喜欢的一种方式是使用 [OSC](/extension_guide/osc/), 由于最新的Adapter内置了 OSC server,所以我们可以轻松将Squeak用作 osc cleint, 消息流向是 `Squeak->Adapter`。
+
+## demo
+
+在Squeak中, 下载 OSC : [OSC-SimonHolland](http://www.squeaksource.com/OSCClient/OSC-SimonHolland.14.mcz), 之后拖到 Squeak 桌面,加载使用即可。
+
+```smalltalk
+(OSCMessage for: {'/eim/osc' . 1}) sendTo: #[127 0 0 1] port: 12361. "turn right"
+
+(OSCMessage for: {'/eim/osc' . 0}) sendTo: #[127 0 0 1] port: 12361. "forward"
+
+(OSCMessage for: {'/eim/osc' . -1}) sendTo: #[127 0 0 1] port: 12361. "turn right"
+```
+
+以上的代码将控制Scratch里的飞行器:
+
+[squeak-scratch-demo](https://scratch-beta.codelab.club/?sb3url=https://adapter.codelab.club/sb3/squeak-scratch-demo.sb3)
+
+
+
+
+## 进阶
+你也可以在Squeak中运行 OSC server,此时消息流向是 `Adapter -> Squeak`
+
+以上代码也可以运行在其他smalltalk方言中,诸如 Pharo。
+
+# 参考
+* [squeak osc](http://wiki.squeak.org/squeak/5836)
\ No newline at end of file
diff --git a/docs/extension_guide/Wekinator.md b/docs/extension_guide/Wekinator.md
new file mode 100644
index 0000000..1fe9b6c
--- /dev/null
+++ b/docs/extension_guide/Wekinator.md
@@ -0,0 +1,9 @@
+# Wekinator
+[Wekinator](http://www.wekinator.org/) 允许任何人使用机器学习来构建新的乐器、手势游戏控制器、计算机视觉或计算机听觉系统等。
+
+Wekinator允许用户通过演示人类的动作和计算机的反应来构建新的交互系统,无需编写代码。
+
+你可以使用 [OSC](/extension_guide/osc/) 插件,与 Wekinator 互操作。
+
+!!! 提醒
+ [Teachable Machine](/extension_guide/teachable_machine/) 可视为Wekinator的后继者,在功能、可理解性和易用性上都做得更好,推荐大家使用。
\ No newline at end of file
diff --git a/docs/extension_guide/alphamini.md b/docs/extension_guide/alphamini.md
new file mode 100644
index 0000000..b9ffb85
--- /dev/null
+++ b/docs/extension_guide/alphamini.md
@@ -0,0 +1,153 @@
+# 悟空机器人(教育版)
+
+
+!!! 提醒
+ 近期优必选官方开放了[标准版的API](http://docs.ubtrobot.com/alphamini/python-sdk/qa.html),你可以通过简单修改Adapter插件支持标准版
+
+# 使用说明
+目前该插件并未内置到 Adapter 中。
+
+我们目前将插件构建为 [Adapter Node](/dev_guide/Adapter-Node/),可以在Adapter外部以普通Python文件运行,一旦运行起来,与普通Adapter插件是一样的,能够与Adapter体系的所有事物交互。
+
+## Python环境
+首先你本地需要有 Python 环境(`Python>=3.6`)
+
+你可以到 [Python 官方](https://www.python.org/)下载,也可以使用 CodeLab放在[国内的版本(Python3.7)](https://www.codelab.club/blog/2020/08/20/tools#python)
+
+!!! 提醒
+ Mac 用户和 Linux 本地很可能内置了 Python3
+
+### 安装依赖
+```bash
+pip install alphamini codelab_adapter_client --upgrade
+```
+
+## 开始!
+
+
+### 步骤 1:打开 [CodeLab Scratch](https://scratch-beta.codelab.club)
+运行CodeLab Adapter, 确保在线平台与Adapte连接正常。
+
+看到 [CodeLab Scratch](https://scratch-beta.codelab.club) 指示灯显示绿色,代表连接成功。
+
+
+
+
+### 步骤 1:运行 node_alphamini.py
+
+将 [node_alphamini.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/nodes_v3/node_alphamini.py) 插件下载到本地(随便放在一个文件夹里),在命令行中进入到这个文件夹,使用 `python node_alphamini.py` 运行它。
+
+### 步骤 2:为 悟空机器人 配网
+
+将 悟空机器人 连上网络。(操作细节可以参考 悟空机器人 说明书)
+
+
+### 步骤 3: 编程
+
+选择 scratch3 中的 EIM 插件.
+
+[alphamini-demo2](https://scratch-beta.codelab.club/?sb3url=https://adapter.codelab.club/sb3/alphamini-demo2.sb3)
+
+以上 Demo 运行结果为:
+
+
+
+
+
+
+
+
+
+
+
+
+# 进阶
+
+更多API参考文档: [mini-python-sdk](https://web.ubtrobot.com/mini-python-sdk/guide.html)
+
+
+
+
+## 悟空的[内置行为](https://web.ubtrobot.com/mini-python-sdk/additional.html)
+
+### [内置舞蹈](https://web.ubtrobot.com/mini-python-sdk/additional.html#id2)
+```python
+robot.play_behavior(name='custom_0035') # 生日快乐
+```
+
+### [内置动作](https://web.ubtrobot.com/mini-python-sdk/additional.html#id3)
+```python
+robot.play_action(action_name='010') # 打招呼
+```
+
+### [内置表情](https://web.ubtrobot.com/mini-python-sdk/additional.html#id4)
+```python
+robot.play_expression(express_name='codemao13') # 疑问
+```
+
+---
+
+!!! 提醒
+ 悟空内部运行一个安卓系统,带有内嵌 Python 环境
+
+
+# FAQ
+## 有些网络无法扫描到悟空
+似乎和局域网内的设备发现机制(mdns)有关, 通常而言,当windows系统切换网络时可能导致mdns服务死掉,通过重启系统(必要时重启悟空)可以解决。
+
+ 通过以下脚本排查问题。如果以下脚本无法扫描到设备,请联系优必选客服人员。
+
+[如何排查 无法发现设备 的问题?](https://adapter.codelab.club/user_guide/FAQ/#_9)
+
+具体解决方案参考[优必选官方文档](http://docs.ubtrobot.com/alphamini/python-sdk/qa.html#demo)
+
+
\ No newline at end of file
diff --git a/docs/extension_guide/blender.md b/docs/extension_guide/blender.md
index fd49479..d80c775 100644
--- a/docs/extension_guide/blender.md
+++ b/docs/extension_guide/blender.md
@@ -3,24 +3,32 @@
### 安装依赖
-我的环境是:`MacOS blender 2.8`
+我的环境是:`MacOS blender 2.92`
+
+
+```bash
+/Applications/Blender.app/Contents/Resources/2.92/python/bin/python3.7m -m pip install codelab_adapter_client --upgrade
+```
### 在 blender 中运行 [node_blender](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/nodes_v3/node_blender.py)
+
-在 blender 2.80 中,我喜欢打开 `Scripting` 标签页(使用 Text Editor 也可以),运行 [node_blender.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/nodes_v3/node_blender.py):
+打开 `Scripting` 标签页,运行 [node_blender.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/nodes_v3/node_blender.py):
!!!提醒
如果你希望看到调试信息(log)
@@ -29,11 +37,11 @@ wget https://bootstrap.pypa.io/get-pip.py

-
+
### 开始使用
@@ -50,3 +58,24 @@ wget https://bootstrap.pypa.io/get-pip.py
# todo
将 [node_blender](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/nodes_v3/node_blender.py) (v2)写成 blender 插件。
+
+# FAQ
+### bpy hello world
+```py
+# 切入blender scripting 面板
+bpy.data.objects["Cube"].location.x += 0.5
+```
+
+
+
+### 基于 OSC 的通信
+
+你也可以使用 Scratch OSC 插件(需要开启Adapter) 与 Blender 通信(需要安装[AddRoutes](http://www.jpfep.net/pages/addroutes/)(在blender 2.92中可用))。
+
+
+
diff --git a/docs/extension_guide/brainCo.md b/docs/extension_guide/brainCo.md
new file mode 100644
index 0000000..b28634a
--- /dev/null
+++ b/docs/extension_guide/brainCo.md
@@ -0,0 +1,7 @@
+# BrainCo
+> Train Your Brain.
+
+
+
+# Demo
+
diff --git a/docs/extension_guide/cozmo.md b/docs/extension_guide/cozmo.md
index 1a71abd..fd84f42 100644
--- a/docs/extension_guide/cozmo.md
+++ b/docs/extension_guide/cozmo.md
@@ -1,22 +1,107 @@
# Cozmo
-!!! 提醒
- 3.2.0 版本的cozmo插件有个错误, 你需要到插件市场下载最新的cozmo插件。
- 我们会在近期的3.2.2版本中修复它
-!!!提醒
- 如果你当前的 Adapter 版本低于`3.1.0`,想在[CodeLab Scratch](https://scratch-beta.codelab.club/)体验最新的event、sensor类型积木(像我们在博客文章[Scratch 拓展最佳实践 -- 以 Cozmo 为例]()里提到的),需要在[插件市场](/extension_guide/extension_market/)里下载最新版本的[node_cozmo.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/nodes_v3/node_cozmo.py)插件。
+## 介绍
+
-# Tutorial
+[Cozmo](https://www.digitaldreamlabs.com/pages/cozmo) 是一个可编程的 AI 机器人
-### install codelab_adapter_client
+这个憨态可掬的机器人,有些像微缩版的瓦力,不过它可没瓦力乖巧
-Python >= `3.6`
+它从睡眼惺忪中醒来,伸伸懒腰,便下床(充电座)自顾自地玩耍,它有自个儿的玩具(发光方块),如果你有时间,愿意陪它做游戏,它会很开心,赢了得意忘形,输了就捶胸顿足,得失心这么重,恐怕不适合炒股
+
+如果你没空陪它,也无妨,它闲庭信步,吹吹口哨、哼哼小曲儿;闲着无聊,便来回搬运自己的玩具,堆叠起来或是一把推翻,自得其乐。除了不尿裤子,其他方面都像极了你六岁时的样子
+
+---
+
+
+## hello world
+
+### 连接
+详尽的文档参考 [人工智能机器人Cozmo的连接说明(by 英荔)](https://adapter.codelab.club/src/8.%20%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD%E6%9C%BA%E5%99%A8%E4%BA%BA%20Cozmo.pdf)
+
+以下是简略说明。
+
+#### 运行 SDK 模式
+将 Cozmo 接入移动设备(手机/平板),并进入 SDK 模式。
+
+使用数据线将手机/平板接入电脑。
+
+详情参考: [官方文档](http://cozmosdk.anki.com/docs/initial.html)
+
+以下是平板设备与电脑的配对信息,有些组合需要安装驱动。
+
+| | Windows | MacOS | Linux |
+| ---- | ---- | ---- | ---- |
+| iOS | [需要安装iTunes, 比较麻烦,不推荐使用](http://cozmosdk.anki.com/docs/install-windows.html#mobile-device-setup)| 开箱可用 | [手动安装](http://cozmosdk.anki.com/docs/install-linux.html) |
+| Android| 开箱可用 | 开箱可用 | [手动安装](http://cozmosdk.anki.com/docs/install-linux.html) |
+
+
+
+
+#### 打开 Scratch Cozmo 插件
+
+
+点击扫描图标, 连接 Cozmo
+
+
+
+让 Cozmo 说出 hello world:
+
+
+
+## 积木说明
+
+
+!!! tip
+ [行为名字API文档](http://cozmosdk.anki.com/docs/generated/cozmo.anim.html#cozmo.anim.Triggers)
+ 浏览这些丰富的行为可以使用这个工具:[Cozmo-Explorer-Tool](https://github.com/GrinningHermit/Cozmo-Explorer-Tool)。
-Linux/MacOS user: `python3 -m pip install codelab_adapter_client --upgrade --user`
+!!! tips
+ [API 文档](http://cozmosdk.anki.com/docs/api.html)
+ [机器人的所有方法](http://cozmosdk.anki.com/docs/generated/cozmo.robot.html#cozmo.robot.Robot)
-windows user: `python -m pip install codelab_adapter_client --upgrade --user`
+基于`执行`积木,你可以轻松构建自定义积木:
-### Install the SDK on your system
+
+
+## 项目链接
+暂无
+
+## FAQ
+### 如何排查 无法发现设备 的问题?
+参考[这里](https://adapter.codelab.club/user_guide/FAQ/#_9)
+
+### 在 notebook 中运行 [cozmo cli](https://github.com/anki/cozmo-python-sdk/blob/master/examples/apps/cli.py)
+
+如果你在notebook中使用,可直接使用内置的notebook: `notebooks/cozmo_lab.ipynb`(最后一个例子)
+
+如果你期待在交互性的 IPython 环境中探索 Cozmo,在Adapter 内置的 jupyterlab 中打开 Terminal,然后使用内置Python解释器运行的 cli 脚本:
+
+* macOS
+ * `./Support/bin/python3 ~/codelab_adapter/src/cozmo_cli.py`
+
+### 如何在 Adapter jupyterlab 中使用
+参考 [Cozmo API](http://cozmosdk.anki.com/docs/api.html),建议在 [jupyterlab](/extension_guide/jupyterlab/) 中做实验(已经内置好了Cozmo环境)
+
+!!! 提醒
+ 如果你希望做一些更复杂的事,建议直接使用社区里的 Python SDK与 设备交互,之后使用 [Adapter Node](/dev_guide/Adapter-Node/) 将其接入Adapter环境中。
+
+### linux 用户如何使用
+
+#### 安装 codelab_adapter_client
+
+Python >= `3.6`
+
+`python3 -m pip install codelab_adapter_client --upgrade --user`
+
+#### 测试运行
Follow Cozmo official tutorial: [Initial Setup](http://cozmosdk.anki.com/docs/initial.html)
@@ -24,12 +109,8 @@ If the following code (`hello_world.py`) runs smoothly, go to the next step.
```python
'''
-MacOS:
- /usr/local/bin/python3 hello_world.py
linux:
/usr/bin/python3 hello_world.py
-Windows:
- python hello_world.py
'''
import cozmo
@@ -41,42 +122,39 @@ def cozmo_program(robot: cozmo.robot.Robot):
cozmo.run_program(cozmo_program)
```
-### Download Codelab Adapter
-
-Download Codelab Adapter
-
-run it
-
-
-
-### Open Scratch 3.0
-
-open [CodeLab Scratch3](https://scratch-beta.codelab.club/)
+## 参考
+* [cozmo系列之入门 - 有性格且可编程的机器人](https://wwj718.github.io/post/%E5%B0%91%E5%84%BF%E7%BC%96%E7%A8%8B/cozmo-hello-world/)
-### Open extension_cozmo
+
\ No newline at end of file
diff --git a/docs/extension_guide/creating-coding.md b/docs/extension_guide/creating-coding.md
new file mode 100644
index 0000000..1969be8
--- /dev/null
+++ b/docs/extension_guide/creating-coding.md
@@ -0,0 +1,26 @@
+# 创意编程(creative-coding)
+> If I can't picture it, I can't understand it. -- Albert Einstein
+
+
+
+创意编程(creative-coding)社区将计算机视为一种表达媒介,这是我们关注该领域的原因。
+
+创意编程(creative-coding)社区有许多流行项目, 我们尤其关注以下几个:
+
+* [mosaic](https://mosaic.d3cod3.org/)
+ * [openFrameworks](https://openframeworks.cc/)
+* [openprocessing](https://www.openprocessing.org/)
+ * Processing
+ * [oscP5](http://www.sojamo.de/libraries/oscP5/)
+ * p5js
+ * [p5js-osc](https://github.com/genekogan/p5js-osc)
+* [Sonic Pi](/extension_guide/sonicPi/)
+* [ptsjs](https://ptsjs.org/)
+* [nannou](https://nannou.cc/)
+
+这些项目大多数都内置 OSC 支持。
+
+由于 Adapter 内置了 [OSC Server 和 Client](/extension_guide/osc/), 所以可以轻松与创意编程(creative-coding)生态的设施互操作!
+
+# 参考
+* [awesome-creative-coding](https://github.com/terkelg/awesome-creative-coding)
\ No newline at end of file
diff --git a/docs/extension_guide/digimon.md b/docs/extension_guide/digimon.md
new file mode 100644
index 0000000..0ca70c2
--- /dev/null
+++ b/docs/extension_guide/digimon.md
@@ -0,0 +1,12 @@
+# digimon
+
+插件介绍: [Neverland2.0原型之 兴趣的【指向】](https://www.codelab.club/blog/2020/12/08/digimon)
+
+
+插件源码: [node_digimon.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/nodes_v3/node_digimon.py).
+
+digimon 插件本质上是webserver(改编(remix)自[node_webserver_flask.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/nodes_v3/node_webserver_flask.py)),将 web 请求转化为 EIM message。
+
+
+# Scratch Demo
+[Scratch digimon demo](https://create.codelab.club/projects/8070/)
\ No newline at end of file
diff --git a/docs/extension_guide/eim.md b/docs/extension_guide/eim.md
index 8c2b370..6aa8f84 100644
--- a/docs/extension_guide/eim.md
+++ b/docs/extension_guide/eim.md
@@ -12,20 +12,13 @@
{!utils/open_scratch.md!}
-## 步骤 3:加载 EIM 插件
+## 步骤 3:打开 Scratch EIM 插件
-在 Web UI 中点击加载 EIM 插件:
+点击加载 Scratch EIM 插件。
-
+
-eim 每秒钟更新一次数值,将数值报告给 Scratch3。
+# 积木介绍
+终点介绍以下几个积木:
-## 步骤 4:hello world
-
-选择对应的 Scratch3 插件:EIM
-
-
-
-我们可以让 Scratch3 的角色读出 EIM 每秒更新一次的数值:
-
-
+
\ No newline at end of file
diff --git a/docs/extension_guide/eim_monitor.md b/docs/extension_guide/eim_monitor.md
index edca207..0cf344f 100644
--- a/docs/extension_guide/eim_monitor.md
+++ b/docs/extension_guide/eim_monitor.md
@@ -67,4 +67,4 @@ def monitor(content,logger):
## 参考
-- [使用 Python 拓展 Scratch 的能力](https://blog.just4fun.site/scratch-adapter-eim-script.html)
+- [使用 Python 拓展 Scratch 的能力](https://wwj718.github.io/scratch-adapter-eim-script.html)
diff --git a/docs/extension_guide/eim_trigger.md b/docs/extension_guide/eim_trigger.md
index adc8e5b..2cf5124 100644
--- a/docs/extension_guide/eim_trigger.md
+++ b/docs/extension_guide/eim_trigger.md
@@ -33,13 +33,11 @@ def trigger():
## 步骤 3:加载 extension_eim_trigger 插件
-在 Web UI 中点击加载 **extension_eim_trigger** 插件:
+在 Web UI 中点击加载 **extension_eim_trigger** 插件
-
-
-开启 **extension_eim_trigger** 插件后,
## 步骤 4:hello world
+开启 **extension_eim_trigger** 插件后
选择对应的 Scratch3 插件:EIM
@@ -80,4 +78,4 @@ def trigger():
## 参考
-- [使用 Python 拓展 Scratch 的能力](https://blog.just4fun.site/scratch-adapter-eim-script.html)
+- [使用 Python 拓展 Scratch 的能力](https://wwj718.github.io/scratch-adapter-eim-script.html)
diff --git a/docs/extension_guide/etoys.md b/docs/extension_guide/etoys.md
new file mode 100644
index 0000000..bf7c54f
--- /dev/null
+++ b/docs/extension_guide/etoys.md
@@ -0,0 +1,93 @@
+# Etoys
+
+## 介绍
+
+
+
+[Etoys](http://www.squeakland.org/) 是:
+
+- 一种教育工具,向孩子们传授强大的思想(powerful ideas)
+- 丰富的媒体创作环境和可视化编程系统
+- 开放系统,几乎适用于所有个人电脑
+
+Etoys 的开发始于迪斯尼,由艾伦·凯(Alan Kay)主导,受 Seymour Papert 的 Logo 语言影响,支持建构主义学习。
+
+开发团队包括:Scott Wallace、Ted Kaehler、John Maloney 和 Dan Ingalls。
+
+极大影响了 Scratch(John Maloney 是 Scratch 的首席架构师)
+
+## 截图
+
+
+
+
+
+## 接入 Adapter
+
+Etoys 的最后一次更新是 2012 年(5.0 版本)。
+
+我们试图通过将其接入 Adatper,使其得到 Adapter 连接的整个生态:物联网、AI、开源硬件... 使 Etoys 强大的表达能力与新的技术融合。
+
+### 思路
+接入的思路是"hack"。
+
+Etoys与外部通信的方式并不多,在 **百宝箱** 里,仅发现了Scratch客户端,可以与外部通信。
+
+
+
+于是我们通过将 Adapter 伪装成 Scratch 1.3,来与Etoys通信,消息流向是(`Etoys->Adatper->Scratch3.0`)
+
+由于百宝箱里的 Scratch客户端是socket client,所以更复杂的通信也是可能的,但`Etoys->Adatper->Scratch3.0`是我自己的典型使用场景。 更多细节参考: [Etoys 学习笔记: 与 Scratch 互操作](https://wwj718.github.io/post/%E5%B0%91%E5%84%BF%E7%BC%96%E7%A8%8B/etoys-learning-note/).
+
+也欢迎你自己进行hack :)
+
+### 使用
+使用方式很简单,在Adapter 中开启 `extension_socket_server` 插件(如果不存在该插件,到[插件市场](/extension_guide/extension_market/)下载即可),源码在[extension_socket_server.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/extensions_v3/extension_socket_server.py)
+
+开启插件后,加载demo程序,并点击绿旗运行它。
+
+[Scratch-Etoys](https://scratch-beta.codelab.club/?sb3url=https://adapter.codelab.club/sb3/Scratch-etoys.sb3)
+
+之后在 Etoys 里使用 broadcask 积木 即可:
+
+
+
+!!! 提醒
+ 在使用具体积木时,需要先建立连接,点击 Etoys里Scratch客户端即可,如果连接顺利,猫的眼睛会睁开。
+
+以下是一个简单的例子:
+
+
+
+使用Etoys里的按钮打开本地目录和CodeLab主页,背后利用到了 Adapter Python Extension的能力:
+
+
+
+一切都是消息!
+
+## Etoys 与 Scratch 对比
+Scratch适合入门,它通过给定清晰的结构,提供更多确定性,让入门变得容易(不必担心搞乱环境)。
+
+随着项目变得更复杂,Etoys是更理想的选择,因其拥有继承自Smalltalk的强大环境和表达能力,惊人的一致性,彻底的面向对象,随着项目逐渐生长,复杂度总是在可控的范围内(因为消息-对象隐喻)。
+
+随着项目变得复杂,Scratch用户需要掌握越来越多的“技巧”,编程成为一件搜罗和记忆许多技巧(特例)的乏味工作(就像传统计算机教育),环境无法提供更多的支持。
+
+Etoys/Squeak 为“Scratch下一步是什么?”提供了理想的答案: `Scratch -> Etoys -> Squeak(Smalltalk)`
+
+## 教育者
+Alan Kay 和 Etoys社区围绕Etoys写了许多精彩的文章,推荐阅读。也许是有史以来关于向孩子传授 powerful ideas 最精彩的文章之一。
+
+诸如:
+
+* [Squeak Etoys Authoring & Media](http://www.squeakland.org/content/articles/attach/etoys_n_authoring.pdf)
+* [Kedama: A GUI-based Interactive Massively Parallel Particle Programming System](http://www.vpri.org/pdf/tr2005001_ohshima_kedama.pdf)
+
+更多文章可以从[Etoy resources](http://www.squeakland.org/resources/articles/) 和 [Viewpoints Research Institute](http://www.vpri.org/)里找
+
+
+## 进阶
+你可以参考[Etoys 学习笔记: 与 Scratch 互操作](https://wwj718.github.io/post/%E5%B0%91%E5%84%BF%E7%BC%96%E7%A8%8B/etoys-learning-note/)自行构建功能更丰富的Server,使用 [Adapter Node](/dev_guide/Adapter-Node/) 将其接入Adapter生态。
+
+## 参考
+
+- [Etoys 学习笔记: 与 Scratch 互操作](https://wwj718.github.io/post/%E5%B0%91%E5%84%BF%E7%BC%96%E7%A8%8B/etoys-learning-note/)
diff --git a/docs/extension_guide/extension_market.md b/docs/extension_guide/extension_market.md
index d940a94..b3901c5 100644
--- a/docs/extension_guide/extension_market.md
+++ b/docs/extension_guide/extension_market.md
@@ -2,11 +2,15 @@
CodeLab Adapter 3.0 有一个统一的插件市场,可以方便下载到新的插件,就像我们在 vscode 或 sublime text 里的体验的那种插件系统,有个体面的 UI,而不是使用 curl 或者 wget 去 github 里手动下载。
-
+
+
+
+
+
作为演示我们下载了插件市场里的一个番茄工作法插件,这个插件的功能很简单(源码也是公开的): 每 25 分钟提醒编程者起来看看窗外风景。
-
+
下载完成之后,不需要重启软件,即可在 Scratch 和 Web UI 中看到新下载的插件,点击运行它:每 25 分钟,你就会收到一条信息提示你做个短途休息。
diff --git a/docs/extension_guide/extension_python_kernel.md b/docs/extension_guide/extension_python_kernel.md
index c7bfe9c..17745b0 100644
--- a/docs/extension_guide/extension_python_kernel.md
+++ b/docs/extension_guide/extension_python_kernel.md
@@ -1,20 +1,13 @@
-# Python eval kernel
+# Python
-## 依赖
+## 介绍
+
-{!utils/dependence.md!}
+此扩展可以将 Python 代码交给 Adapter 执行(eval),并获取结果。
-## 步骤 1:打开 Codelab Adapter
+## hello world
-{!utils/open_adapter.md!}
-
-## 步骤 2:打开 Codelab Scratch3
-
-{!utils/open_scratch.md!}
-
-## 步骤 3:hello world
-
-选择对应的 Scratch3 插件:Python 插件
+打开 Scratch Python 插件

@@ -26,7 +19,14 @@

-Python 插件将以 [eval](https://docs.python.org/zh-cn/3.7/library/functions.html#eval) 执行 Python 代码(只能执行表达式),如果你想使用 exec ,文末有指导。
+## 积木说明
+暂无
+
+## 进阶
+
+### 使用技巧
+
+#### 自定义积木
你可以在插件中添加新的类,来自定义新功能, 我们做了一个范例: [PyHelper 源码](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/extensions_v3/extension_python.py#L18)。你可以使用 `PyHelper.open_url("https://www.codelab.club/")`来为 Scratch 引入打开网页的功能。
@@ -48,11 +48,47 @@ Python 插件将以 [eval](https://docs.python.org/zh-cn/3.7/library/functions.h
你可以在插件里添加更多的类似`PyHelper`的自定义类,来为 Scratch 引入更多新的能力,使用 Python 就行!
-## 延伸
+#### 与网络交互(requests)
+
+考虑到网络极为强大,Python 插件允许用户在 Scratch 中直接与 requests 交互(只支持表达式,形如`requests.get(...), requests.post(...)`)。
+
+由于 requests 的 API 极为漂亮(支持链式风格),你几乎可以单个表达式中干绝大多数的事情!
+
+[requests 文档](https://docs.python-requests.org/zh_CN/latest/)。
+
+一些典型的用例包括:
+
+* [两个 Adapter 之间的消息通信](https://adapter.codelab.club/user_guide/%E4%B8%8E%E5%A4%96%E9%83%A8%E7%B3%BB%E7%BB%9F%E9%80%9A%E4%BF%A1/#requests)
+* 触发 webhook
+* 请求[公开的网络 API](https://github.com/public-apis/public-apis)
+* 与自己搭建的 web server 交互
+
+##### demo
+```py
+requests.get("http://httpbin.org/get")
+# 链式写法
+requests.get("http://httpbin.org/get").text
+requests.get("http://httpbin.org/get").json()
+
+# 携带参数
+requests.get("http://httpbin.org/get", params={"name": "codelab"})
+
+# post
+requests.post('http://httpbin.org/post', data = {'key':'value'})
+```
+
+##### 一些奇怪的想法
+* 通过与带有存储能力的 API 结合,可以构建出类似云变量这种东西
+ * 或者使用 [json-server](https://github.com/typicode/json-server) 临时搭建一个
+
+### 项目链接
+
+
+#### 文件储存案例
再来做一个例子,[@HansonXie](http://www.concentric-circle.com/author/admin/) 给我写了封邮件,说想写一个extension或者node来进行文件存储,希望用Python来做,而不是Javascript。在此我写个简单例子
-我们可以使用 [Jupyterlab](https://adapter.codelab.club/extension_guide/jupyterlab/) 编辑[extension_python.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/extensions_v3/extension_python.py). 在其中增加一个新的助手类,来负责存储文件
+我们可以使用 [Jupyterlab](/extension_guide/jupyterlab/) 编辑[extension_python.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/extensions_v3/extension_python.py). 在其中增加一个新的助手类,来负责存储文件
```python
class StorageHelp:
@@ -86,7 +122,12 @@ eval(code, {"__builtins__": None}, {

-# 最后
+## FAQ
+### 如何工作
+
+!!! 提醒
+ exec 可能带来各种安全风险,此外,eval也更符合我们采用的`对象/消息`隐喻。
+ 如果你确实需要exec,可以自行构建插件, 参考[python_exec.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/extensions_v3/python_exec.py)
内置在 Adapter 里的 Python 插件以 [eval](https://docs.python.org/zh-cn/3.7/library/functions.html#eval) 执行 Python 代码(只能执行表达式),如果你希望以功能更轻大的 [exec](https://docs.python.org/zh-cn/3.7/library/functions.html#exec) (可执行任何 Python 语句)执行 Python 代码,可以在[插件市场](/extension_guide/extension_market/)里下载 [extension_python_exec 插件](/extension_guide/python_exec/)。
@@ -98,11 +139,11 @@ eval(code, {"__builtins__": None}, {
我们之没有将 extension_python_exec 内置在 Adapter 中,而是希望用户在需要时自行下载,因为它的功能过于强大,可能会带来一下风险,所以选择权交由使用者。强大的能力通常会伴随风险,当然我们不会做太多限制,由你决定:)
-# 参考
+## 参考
-- [将 codelab-adapter 用作 Python 解释器](https://blog.just4fun.site/scratch3-adapter-as-python-interpreter.html)
+- [将 codelab-adapter 用作 Python 解释器](https://wwj718.github.io/scratch3-adapter-as-python-interpreter.html)
diff --git a/docs/extension_guide/ha.md b/docs/extension_guide/ha.md
index 6e3970e..7bf3208 100644
--- a/docs/extension_guide/ha.md
+++ b/docs/extension_guide/ha.md
@@ -1,4 +1,4 @@
# Tutorial
hello world
-参考[安装和配置 Home Assistant](https://adapter.codelab.club/Neverland/HA/)
\ No newline at end of file
+参考[安装和配置 Home Assistant](/Neverland/HA/)
\ No newline at end of file
diff --git a/docs/extension_guide/halocode.md b/docs/extension_guide/halocode.md
new file mode 100644
index 0000000..3b42788
--- /dev/null
+++ b/docs/extension_guide/halocode.md
@@ -0,0 +1,4 @@
+# 光环板
+
+
+光环板[支持mqtt](https://makeblock-micropython-api.readthedocs.io/zh/latest/public_library/Third-party-libraries/mqtt.html), 可通过[mqtt协议接入](https://adapter.codelab.club/extension_guide/iot/)
\ No newline at end of file
diff --git a/docs/extension_guide/imageData.md b/docs/extension_guide/imageData.md
index 28262f1..a0f2f9b 100644
--- a/docs/extension_guide/imageData.md
+++ b/docs/extension_guide/imageData.md
@@ -1,2 +1,24 @@
# Tutorial
-hello world
\ No newline at end of file
+用于获取和设置舞台区数据,具体而言:
+
+* 获取当前舞台图像、当前视频图像
+ * 这些数据的消费者包括:
+ * [extension_stage.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/extensions_v3/extension_stage.py)
+ * [node_physical_blocks2.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/nodes_v3/node_physical_blocks2.py)
+* 获取/设置当前角色的自定义造型
+
+# demo
+大多数的 physical blocks 项目都使用到了`获取当前视频图像`.
+
+而以下项目使用到了`获取/设置当前角色的自定义造型`:
+
+
+
+
+
+## 与 physical blocks 2.0 配合使用
+
+
+
+!!! 提醒
+ 如果你想切换分辨率,Adapter的版本需要`>=3.7.4`, 或者到插件市场下载最新的physical blocks 2.0插件。 此外值得注意的是,分辨率越高,刷新率越慢(很大原因是因为传输的数据大造成的)
\ No newline at end of file
diff --git a/docs/extension_guide/intelino.md b/docs/extension_guide/intelino.md
new file mode 100644
index 0000000..c429005
--- /dev/null
+++ b/docs/extension_guide/intelino.md
@@ -0,0 +1,23 @@
+# intelino
+
+入门用户推荐使用最新的 [mu-editor](https://codewith.mu/en/download) 编辑器
+
+如果你使用其他 Python 环境,请确保`Python >= 3.8`
+
+## windows
+
+需要 windows10 或 windows11
+
+windows10: `pip install intelino-trainlib bleak==0.12.1`
+
+windows11: `pip install intelino-trainlib`
+
+## macOS & Linux
+
+`pip install intelino-trainlib`
+
+## 相关资料
+
+- 源码: [intelino-trainlib-py](https://github.com/intelino-code/intelino-trainlib-py)
+ - 案例: [examples](https://github.com/intelino-code/intelino-trainlib-py/tree/master/examples)
+- 文档: [readthedocs](https://intelino-trainlib-async-py.readthedocs.io/en/latest/index.html)
diff --git a/docs/extension_guide/iot.md b/docs/extension_guide/iot.md
index 28262f1..6c97955 100644
--- a/docs/extension_guide/iot.md
+++ b/docs/extension_guide/iot.md
@@ -1,2 +1,60 @@
-# Tutorial
-hello world
\ No newline at end of file
+# MQTT 插件
+MQTT broker 需要支持 wss(websockets) 协议, 才能在Scratch里连接它
+
+CodeLab合作伙伴[英荔教育](https://aimaker.space/about)为CodeLab社区用户提供一个免费 MQTT broker:
+
+
+* url: mqtt.longan.link
+* 默认用户名/密码: guest/test
+* tcp port: 1883
+* tls port 8883
+* websockets port: 8084
+
+# Demo
+## scratch client
+[scratch demo](https://create.codelab.club/projects/22163/editor/)
+
+## python client
+基于 [paho-mqtt](https://github.com/eclipse/paho.mqtt.python)
+
+```py
+import paho.mqtt.client as mqtt
+
+# The callback for when the client receives a CONNACK response from the server.
+def on_connect(client, userdata, flags, rc):
+ # print("Connected with result code "+str(rc))
+ if rc == 0:
+ print('已连接')
+ else:
+ print('连接出错!')
+ # Subscribing in on_connect() means that if we lose the connection and
+ # reconnect then subscriptions will be renewed.
+ client.subscribe("test")
+
+# The callback for when a PUBLISH message is received from the server.
+def on_message(client, userdata, msg):
+ print(msg.topic+" "+str(msg.payload))
+
+client = mqtt.Client()
+client.on_connect = on_connect
+client.on_message = on_message
+client.username_pw_set('guest', 'test')
+
+client.connect("mqtt.longan.link", 1883, 60)
+
+# Blocking call that processes network traffic, dispatches callbacks and
+# handles reconnecting.
+# Other loop*() functions are available that give a threaded interface and a
+# manual interface.
+client.loop_forever() # client.loop_start() 是非阻塞的
+```
+
+## MicroBlocks demo
+[MicroBlocks demo](https://microblocks.codelab.club/#scripts=GP%20Scripts%0Adepends%20%27MQTT%27%20%27WiFi%27%0A%0Ascript%20429%20-138%20%7B%0AwhenStarted%0Acomment%20%271.%20connect%20wifi%27%0A%27wifi%20connect%20to%27%20%27Elite_1006%27%20%2720130530%27%203%0AwaitMillis%201000%0Acomment%20%272.%20connect%20MQTT%20broker%27%0A%27MQTT%20connect%20to%27%20%27mqtt.longan.link%27%20128%20%27MicroBlocks_client%27%20%27guest%27%20%27test%27%0Acomment%20%27subscribe%20topic%27%0A%27MQTT%20sub%27%20%27microblocks%27%0AsendBroadcast%20%27go%21%27%0A%7D%0A%0Ascript%20429%20155%20%7B%0AwhenBroadcastReceived%20%27go%21%27%0Acomment%20%27receive%20mqtt%20message%27%0Aforever%20%7B%0A%20%20if%20%28%27MQTT%20connected%27%29%20%7B%0A%20%20%20%20event%20%3D%20%28%27last%20MQTT%20event%27%29%0A%20%20%20%20if%20%28isType%20event%20%27list%27%29%20%7B%0A%20%20%20%20%20%20sayIt%20%27topic%27%20%28%27MQTT%20event%20topic%27%20event%29%20%27%2C%20payload%27%20%28%27MQTT%20event%20payload%27%20event%29%0A%20%20%20%20%7D%0A%20%20%7D%20else%20%7B%0A%20%20%20%20sayIt%20%27try%20to%20connect%20...%27%0A%20%20%20%20waitMillis%20500%0A%20%20%20%20%27MQTT%20connect%20to%27%20%27mqtt.longan.link%27%20128%20%27MicroBlocks_client%27%20%27guest%27%20%27test%27%0A%20%20%7D%0A%7D%0A%7D%0A%0Ascript%20581%20120%20%7B%0Acomment%20%27publish%20topic%20payload%27%0A%27MQTT%20pub%27%20%27scratch%27%20%27Hello%21%27%0A%7D%0A%0Ascript%20403%20-168%20%7B%0Acomment%20%27work%20with%20https%3A%2F%2Fcreate.codelab.club%2Fprojects%2F22163%2Feditor%2F%27%0A%7D%0A%0A)
+
+
+# 参考
+* [MQTT.js](https://github.com/mqttjs/MQTT.js): 提供 cli 工具
+* [mosquitto](https://github.com/eclipse/mosquitto)
+* [paho-mqtt](https://github.com/eclipse/paho.mqtt.python)
+* [gmqtt](https://github.com/wialon/gmqtt)
\ No newline at end of file
diff --git a/docs/extension_guide/jupyterlab.md b/docs/extension_guide/jupyterlab.md
index 14d0777..69dffbf 100644
--- a/docs/extension_guide/jupyterlab.md
+++ b/docs/extension_guide/jupyterlab.md
@@ -1,6 +1,8 @@
-# [JupyterLab](https://jupyterlab.readthedocs.io/en/stable/)
+# JupyterLab
+
-JupyterLab 是 jupyter notebook 的下一代产品。
+## 介绍
+[JupyterLab](https://jupyterlab.readthedocs.io/en/stable/) 是 jupyter notebook 的下一代产品。
Jupyter Notebook(前身是IPython Notebook)是一个基于Web的交互式计算环境。
@@ -16,24 +18,13 @@ CodeLab Adapter 将其集成到插件,如果想编辑代码,诸如
或者任何时候你想学习 Python ,JupyterLab 都是你理想的伙伴。
-# Tutorial
-## 依赖
+## hello world
+在 webUI 里运行 **extention_jupyterlab** 插件。
-{!utils/dependence.md!}
+
-## 步骤 1:打开 Codelab Adapter
+---
-{!utils/open_adapter.md!}
-
-运行 **extention_jupyterlab** 插件。
-
-!!! 提醒
- 完整版已经内置了一切依赖。
- 如果你是有精简版,确保你已经[安装了 Python3](/Python_Projects/install_python/)。
- 你不必手动安装 **jupyterlab** ,运行插件,CodeLab Adapter 会为你其余的一切。
- 当然你也可以在命令行里手动安装它。
-
-## 步骤 2:使用 jupyterlab
jupyterlab 默认将打开 [Adapter主目录](/user_guide/FAQ/#adapter)。

@@ -48,3 +39,92 @@ jupyterlab 默认将打开 [Adapter主目录](/user_guide/FAQ/#adapter)。
6. Adapter 运行日志,当你需要[调试](/dev_guide/debug/) extension 时,查看日志将很有帮助。
你可以使用 jupyterlab 随意修改它们。别担心改坏了。如果发现修改后 Adapter无法正常运行。则将整个[ Adapter 主目录](/user_guide/FAQ/#adapter)删除即可,重启 Adapter,你将得到一个崭新的 Adapter 主目录。它是为你学习而构建的环境,别担心玩坏它,尽情探索吧。
+
+## 积木说明
+暂无
+
+## 项目链接
+暂无
+
+## FAQ
+
+### 启用 Jupyterlab 插件,没有自动打开 Jupyterlab
+可能是因为你的系统用户名(windows系统)是中文,目前 Jupyterlab 存在这个 bug, 官方正在修复中。
+
+我们目前给出了一个手动打开方案:
+
+1. 启动 Jupyterlab 插件
+2. 稍等 3-5秒, 之后在浏览器里打开`localhost:8888` (如果打不开,则试试`localhost:8889`)
+3. 复制 `Adapter token` 到 Jupyterlab 登陆框里
+
+
+
+
+
+### 安装第三方库
+```py
+import pip
+# 举个例子: 安装 furl
+pip.main(["install", "furl"])
+# 你也可以使用国内的源:
+# pip.main(['install', 'furl', '-i', 'https://mirrors.aliyun.com/pypi/simple'])
+# 针对ssl证书有问题的用户 pip.main(['install', 'furl', '-i', 'http://mirrors.aliyun.com/pypi/simple', "--trusted-host", "mirrors.aliyun.com"])
+```
+
+安装完之后,需要在 jupyterlab 重启kernel,也可以重启 jupyterlab。
+
+### 列出所有库
+```py
+import pip
+pip.main(["freeze"])
+```
+
+### 汉化
+`Adapter >= 3.3.1`,jupyterlab 版本升级到 3.0,支持切换语言:
+
+
+
+
+### 精简版(linux/RPI)如何安装 jupyterlab
+完整版已经内置了一切依赖。
+
+如果你是有精简版,确保你已经[安装了 Python3](/Python_Projects/install_python/)。
+
+你不必手动安装 **jupyterlab** ,运行插件,CodeLab Adapter 会为你其余的一切。
+
+当然你也可以在命令行里手动安装它。
+
+### 如何启动实时协作模式
+[实时协作模式](https://jupyterlab.readthedocs.io/en/stable/user/rtc.html)对于结对编程、远程教学以及课堂教学可能都有帮助。
+
+启动实施模式的方法是,使用 jupyterlab 打开 extensions 目录里的 `extension_jupyterlab.py` 插件,将 `self.allow_collaborative`改为True。 之后重启 Jupyterlab 插件。
+
+此时,同一个局域网里的任何电脑都可以进入同一个 Jupyterlab 里,进行实时协作。
+
+具体方法是:
+
+1. 在其他电脑上的浏览器里(可以是移动设备!),打开启用实时协作的Jupyterlab的地址入口(形如`192.168.31.100:8888`), 之后输入token,token与Adapter token一样。
+2. 结对编程者打开同一个notebook文件(或者 `.py` 文件)。
+
+### 运行 Python 脚本
+
+在 Jupyterlab 中打开 **终端**.
+
+MacOS:
+
+`./Support/bin/python3 ./adapter_home/notebooks/hello.py`
+
+Windows:
+
+`.\src\python\python .\src\adapter_home\notebooks\hello.py`
+
+### 为何 MacOS 下无法在jupyterlab使用摄像头(如在opencv中)
+
+最近几个版本的 MacOS 对权限管理非常严格,需要从命令行启动 Adapter(允许访问摄像头)
+
+
+### 更新Adapter后,Jupyterlab页面显示白色
+
+是因为jupyterlab版本升级造成的。
+
+删除 `C:\Users\
+
+之后进入 interface,开始操作即可。
+
+输出的 osc 信号将进入 Adapter,继而可以在 Scratch 的 OSC 积木中访问它。
+
+一则 osc 消息由 2 部分构成:
+
+- address(地址,类似 url)
+- args(参数,是个 list), 每一个参数的含义请参考具体 OSC 软件的文档说明
+
+address 和 args 可以在具体 osc 软件中找到。
+
+## FAQ
+
+### 有什么推荐的 OSC 面板(client)
+我目前最喜欢的 OSC client 是:
+
+* `Syntien`
+* `Unipad`
+
+#### Syntien
+Syntien 提供了丰富的控制面板
+
+
+
+
+
+
+
+它甚至允许你自定义面板!
+
+
+
+
+
+#### Unipad
+
+
+
+
+
+
+
+
+Unipad 提供多种游戏手柄界面,这些可以很好地跟Scratch项目结合! OSC 如此高的刷新率,几乎没有任何延迟
+
+### 有什么推荐的兼容 OSC 的软件
+
+* 音乐: [SonicPi](/extension_guide/sonicPi/)
+* AI:[Wekinator](http://www.wekinator.org/)
+* blender/unity
\ No newline at end of file
diff --git a/docs/extension_guide/overdrive.md b/docs/extension_guide/overdrive.md
new file mode 100644
index 0000000..76831b3
--- /dev/null
+++ b/docs/extension_guide/overdrive.md
@@ -0,0 +1,48 @@
+# Overdrive
+
+## 介绍
+
+
+> 与你的朋友或人工智能来一场速度与激情的较量。
+
+Overdrive 是一款可编程赛车,内置多种传感器,在特制的跑道上运行时能定位到自身的位置。此扩展可以实时控制通过蓝牙连接的赛车。
+
+## hello world
+打开 Scratch overdrive 插件。
+
+使用连接图标,连接小车。
+
+跑起来:
+
+
+
+## 积木说明
+暂无
+
+## 项目链接
+### Demo 1: microbit 无线油门
+
+
+
+
+[microbit-overdrive](https://scratch-beta.codelab.club/?sb3url=https://adapter.codelab.club/sb3/microbit-overdrive.sb3)
+
+
+### Demo 2
+
+
+
+
+
+[overdrive-demo](https://scratch-beta.codelab.club/?sb3url=https://adapter.codelab.club/sb3/overdrive-demo.sb3)
+
+## FAQ
+### PRO_KEY是什么?如何获取?
+PRO_KEY 是 CodeLab 尝试服务合作伙伴和企业用户的高级特性。
+
+使用 PRO_KEY 可以启用 Adapter 的高级特性: 激光雷达、overdrive...
+
+欢迎发送邮件咨询/合作/购买: `wuwenjie@codelab.club`
+
+### [overdrive 居中运行](https://create.codelab.club/projects/10388/) 有时会一直摇摆
+是的,造成这个问题的原因是,小车有时报告的offset信息不准确,目前认为是固件问题,暂无办法。
\ No newline at end of file
diff --git a/docs/extension_guide/overdrive2.md b/docs/extension_guide/overdrive2.md
new file mode 100644
index 0000000..1b5fab6
--- /dev/null
+++ b/docs/extension_guide/overdrive2.md
@@ -0,0 +1,2 @@
+# overdrive
+参考[overdrive](https://adapter.codelab.club/extension_guide/overdrive/)
\ No newline at end of file
diff --git a/docs/extension_guide/physical_blocks.md b/docs/extension_guide/physical_blocks.md
index dab09c6..f718586 100644
--- a/docs/extension_guide/physical_blocks.md
+++ b/docs/extension_guide/physical_blocks.md
@@ -18,36 +18,24 @@ physical blocks 在软件层面是一个 CodeLab Adapter插件,由于 CodeLab
* 运行程序!
-->
+!!! 提醒
+ 建议使用2.0版本: [physical blocks 2.0](/extension_guide/physical_blocks2/)。 不久将弃用1.0版本。
+
使用 physical blocks,可以在一张桌子上对实物进行编程。
+Windows 和 Mac 用户开箱可用。 Linux需要安装依赖(参考文末)
+
+建议以[入门案例](#_3)为模版。
+
参考:
- [CodeLab DynamicTable: A Seeing World](https://www-old.codelab.club/blog/codelab-dynamictable-a-seeing-world/)
- [CodeLab DynamicTable: 一个可实施的技术方案](https://www-old.codelab.club/blog/codelab-dynamictable-an-instance/)
-## 提醒
-
-第一次运行插件,Windows 和 Mac 用户会自行按照依赖: `opencv-contrib-python`, 依赖比较大(`> 60MB`), 耐心等待 1 分钟左右,安装完成会会弹出通知。
-
-Linux 用户需要手动安装 `opencv-contrib-python`(有系统依赖)。
-
-### 树莓派用户
-
-安装依赖系统
-
-```bash
-sudo apt-get install libjpeg-dev libtiff5-dev libjasper-dev libpng12-dev
-sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev
-sudo apt-get install libxvidcore-dev libx264-dev
-sudo apt install libatlas-base-dev
-sudo apt-get install qt4-dev-tools
-sudo apt-get install libhdf5-dev libhdf5-serial-dev libhdf5-103
-```
-
-之后安装`opencv-contrib-python`
-
-- `pip3 install opencv-contrib-python==3.4.6.27`
+## 积木介绍
+
+可以从 [arucogen](https://chev.me/arucogen/) 查询 ArUco marker id
## Demo
@@ -65,7 +53,7 @@ sudo apt-get install libhdf5-dev libhdf5-serial-dev libhdf5-103
- [Scratch-spell-demo.sb3](https://scratch-beta.codelab.club/?sb3url=https://adapter.codelab.club/sb3/Scratch-spell-demo.sb3){target=\_blank} : 获取 marker id 列表(从左到右,从上到下)
## 更多案例
-* [第一期的直播演示项目](https://www.codelab.club/blog/the-first-live-showcase-projects-code/)
+* [第一期的直播演示项目](https://www-old.codelab.club/blog/the-first-live-showcase-projects-code/)
* [智能家居展厅](https://scratch-beta.codelab.club/?sb3url=https://adapter.codelab.club/sb3/Scratch-spelling-iot-show.sb3)
# FAQ
@@ -73,7 +61,7 @@ sudo apt-get install libhdf5-dev libhdf5-serial-dev libhdf5-103
### 如何打印 Marker
我们提供了一份30张的版本:
-
+
你可以从 [arucogen](https://chev.me/arucogen/) 里打印(建议从编号1开始)
@@ -89,4 +77,25 @@ sudo apt-get install libhdf5-dev libhdf5-serial-dev libhdf5-103
aruco_dict = cv2.aruco.Dictionary_get(cv2.aruco.DICT_4X4_100)
```
-关于不同的marker数量决定了字典的大小,越小的数字,鲁棒性越好。 考虑到大多数用户的场景,50个是够用的,所以我们默认采用50.
\ No newline at end of file
+关于不同的marker数量决定了字典的大小,越小的数字,鲁棒性越好。 考虑到大多数用户的场景,50个是够用的,所以我们默认采用50.
+
+### Linux 用户
+
+Linux 用户需要手动安装 `opencv-contrib-python`(有系统依赖)。
+
+#### 树莓派用户
+
+安装依赖系统
+
+```bash
+sudo apt-get install libjpeg-dev libtiff5-dev libjasper-dev libpng12-dev
+sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev
+sudo apt-get install libxvidcore-dev libx264-dev
+sudo apt install libatlas-base-dev
+sudo apt-get install qt4-dev-tools
+sudo apt-get install libhdf5-dev libhdf5-serial-dev libhdf5-103
+```
+
+之后安装`opencv-contrib-python`
+
+- `pip3 install opencv-contrib-python==3.4.6.27`
diff --git a/docs/extension_guide/physical_blocks2.md b/docs/extension_guide/physical_blocks2.md
new file mode 100644
index 0000000..b3aae75
--- /dev/null
+++ b/docs/extension_guide/physical_blocks2.md
@@ -0,0 +1,106 @@
+# Tutorial
+
+## 介绍
+physical blocks 2.0,新的更新我们都将在这个版本是进行,[physical blocks 1.0](/extension_guide/physical_blocks/)在未来将弃用。
+
+使用 physical blocks,可以在一张桌子上对实物进行编程。
+
+Windows 和 Mac 用户开箱可用。 Linux需要安装依赖(参考文末)
+
+!!! 提醒
+ CodeLab Adapter版本 `>= 3.7.3`
+
+参考:
+
+- [CodeLab DynamicTable: A Seeing World](https://www-old.codelab.club/blog/codelab-dynamictable-a-seeing-world/)
+- [CodeLab DynamicTable: 一个可实施的技术方案](https://www-old.codelab.club/blog/codelab-dynamictable-an-instance/)
+
+## 积木介绍
+
+
+
+可以从 [arucogen](https://chev.me/arucogen/) 查询 ArUco marker id
+
+## Demo
+
+
+
+
+
+
+
+## 入门案例
+
+分享两个入门案例:
+
+- [physical-blocks2-angle-demo](https://scratch-beta.codelab.club/?sb3url=https://adapter.codelab.club/sb3/physical-blocks2-angle-demo.sb3){target=\_blank} : 获取 marker 旋转角
+- [physical-blocks2-spell-demo](https://scratch-beta.codelab.club/?sb3url=https://adapter.codelab.club/sb3/physical-blocks2-spell-demo.sb3){target=\_blank} : 获取 marker id 列表(从左到右,从上到下)
+
+## 更多案例
+* [第一期的直播演示项目](https://www-old.codelab.club/blog/the-first-live-showcase-projects-code/)
+* [智能家居展厅](https://scratch-beta.codelab.club/?sb3url=https://adapter.codelab.club/sb3/Scratch-spelling-iot-show.sb3)
+
+# FAQ
+
+### 刷新率/FPS
+在 MacOS(2.3 GHz Intel Core i5)下,大约达到10帧的刷新率
+
+目前尚未发布的 Pro 版本大约是 20 帧的刷新率
+
+可使用以下技巧观察刷新率:
+
+
+
+### 与1.0版本的区别?
+标记列表默认是字符串(序列化之后),可以随意与scrath积木组合(诸如`xx包含xx`积木),避免因为操作list引起的崩溃(诸如将list保存为变量)。
+
+在2.0中,直到主动使用JSON parse积木解析后,它才称为列表。相关操作参考:[json积木](/extension_guide/json/),也可参考前边的例子: [physical-blocks2-spell-demo](https://scratch-beta.codelab.club/?sb3url=https://adapter.codelab.club/sb3/physical-blocks2-spell-demo.sb3){target=\_blank}
+
+此外2.0 只使用一个积木更新数据,提高标签存在的稳定性(也提高速度),其中一种典型的编程模式是: 在一次更新数据之后的积木都对应更新瞬间的视野状态。而不像1.0,每个积木都有各自瞬间的视野。
+
+### 如何打印 Marker
+我们提供了一份30张的版本:
+
+
+
+你可以从 [arucogen](https://chev.me/arucogen/) 里打印(建议从编号1开始)
+
+更多细节参考[CodeLab DynamicTable: 一个可实施的技术方案](https://www-old.codelab.club/blog/codelab-dynamictable-an-instance/)
+
+### 默认的Marker支持250种不同类型(marker id)
+默认是`4X4_250`(最多250种)的marker, 你可以选择:
+
+* `4x4_50`(最多50种)
+* `4x4_250`(最多250种)
+* `4x4_100`(最多100种)
+* `4x4_1000`(最多1000种)
+
+选择之后请修改(推荐使用[JupyterLab](/extension_guide/jupyterlab/))插件里对应的代码(104行):
+
+```python
+# aruco_dict = cv2.aruco.Dictionary_get(cv2.aruco.DICT_4X4_250)
+aruco_dict = cv2.aruco.Dictionary_get(cv2.aruco.DICT_4X4_1000)
+```
+
+关于不同的marker数量决定了字典的大小,越小的数字,鲁棒性越好。
+
+### Linux 用户
+
+Linux 用户需要手动安装 `opencv-contrib-python`(有系统依赖)。
+
+#### 树莓派用户
+
+安装依赖系统
+
+```bash
+sudo apt-get install libjpeg-dev libtiff5-dev libjasper-dev libpng12-dev
+sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev
+sudo apt-get install libxvidcore-dev libx264-dev
+sudo apt install libatlas-base-dev
+sudo apt-get install qt4-dev-tools
+sudo apt-get install libhdf5-dev libhdf5-serial-dev libhdf5-103
+```
+
+之后安装`opencv-contrib-python`
+
+- `pip3 install opencv-contrib-python==3.4.6.27`
diff --git a/docs/extension_guide/pi-top.md b/docs/extension_guide/pi-top.md
new file mode 100644
index 0000000..306d5ff
--- /dev/null
+++ b/docs/extension_guide/pi-top.md
@@ -0,0 +1,102 @@
+# pi-top
+
+# 介绍
+
+
+
+## 接管 pi-top 的思路
+
+参考[机械臂](https://adapter.codelab.club/extension_guide/robotic-arm/),因其都是树莓派。
+
+
+## hello world
+
+### 用户电脑配置
+
+假设用户使用自己的电脑编程(运行 Adapter 和 Scratch)
+
+在运行Adapter之前,设置[配置项](https://adapter.codelab.club/user_guide/settings/) `OPEN_MESSAGE_HUB = true`
+
+之后运行 CodeLab Adapter.
+
+### 树莓派配置
+
+在机械臂树莓派里安装 [codelab_adapter_client_python](https://github.com/CodeLabClub/codelab_adapter_client_python): `pip3 install codelab_adapter_client`
+
+创建 `node_PiTop.py` :
+
+```py
+# fork 自: https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/extensions_v3/extension_python.py
+import time
+from loguru import logger
+
+from codelab_adapter_client import AdapterNode
+
+from pitop import Pitop
+
+
+# Set up pi-top
+pitop = Pitop()
+
+# Say hi!
+# pitop.miniscreen.display_text("Hello!")
+
+class PythonKernelExtension(AdapterNode):
+
+ NODE_ID = "eim/PiTop"
+ HELP_URL = "http://adapter.codelab.club/"
+ WEIGHT = 95
+ VERSION = "0.1" # extension version
+ DESCRIPTION = "PiTop"
+
+ def __init__(self, **kwargs):
+ adapter_host = "192.168.31.164" # 运行Adapter的计算机IP
+ super().__init__(codelab_adapter_ip_address = adapter_host, **kwargs)
+ # self.PyHelper = PyHelper()
+
+ def run_python_code(self, code):
+ try:
+ # eval(expression, globals=None, locals=None)
+ output = eval(code, {"__builtins__": None}, {
+ # "PyHelper": self.PyHelper,
+ # "requests": requests,
+ "pitop": pitop,
+ })
+ except Exception as e:
+ output = str(e)
+ return output
+
+ def extension_message_handle(self, topic, payload):
+ logger.info(f'python code: {payload["content"]}')
+ python_code = payload["content"]
+ output = self.run_python_code(python_code)
+ try:
+ output = str(output) # 不要传递复杂结构
+ except Exception as e:
+ output = str(e)
+ payload["content"] = output
+ message = {"payload": payload}
+ self.publish(message)
+
+ def run(self):
+ "避免插件结束退出"
+ while self._running:
+ time.sleep(0.5)
+
+node = PythonKernelExtension()
+node.receive_loop_as_thread()
+node.run()
+```
+
+
+在树莓派里运行它。
+
+之后即可在 CodeLab Scratch 对pi-top编程。
+
+参考这个 [Demo](https://create.codelab.club/projects/12380/)
+
+
+
+
+## 参考
+* [API - pi-top Device](https://pi-top-pi-top-python-sdk.readthedocs-hosted.com/en/stable/api_pitop_device.html#pitop)
\ No newline at end of file
diff --git a/docs/extension_guide/pico.md b/docs/extension_guide/pico.md
new file mode 100644
index 0000000..583f020
--- /dev/null
+++ b/docs/extension_guide/pico.md
@@ -0,0 +1,9 @@
+# Pico
+
+
+# Demo
+
+
+
+
+[Hello RPI Pico](https://create.codelab.club/projects/9012/)
\ No newline at end of file
diff --git a/docs/extension_guide/posenet.md b/docs/extension_guide/posenet.md
index 28262f1..86630e2 100644
--- a/docs/extension_guide/posenet.md
+++ b/docs/extension_guide/posenet.md
@@ -1,2 +1,21 @@
# Tutorial
-hello world
\ No newline at end of file
+hello world
+
+# 进阶
+
+## PoseNet 精度测试(By 尚老师)
+
+### 0 摘要
+PoseNet作为姿态识别重要的模块之一(如图 1所示),在使用时会出现数据跳点,因此在使用时,会对部分操作产生影响。通过研究发现,提高数据精度的最有效想法之一,就是根据置信度对数据进行筛选。
+
+
+
+
+
+# FAQ
+
+## 数据抖动
+可以考虑加上置信度, 参考 [置信度demo](https://create.codelab.club/projects/9767/)
+
+## 左右颠倒
+开启摄像头试试
\ No newline at end of file
diff --git a/docs/extension_guide/readme.md b/docs/extension_guide/readme.md
new file mode 100644
index 0000000..e50537b
--- /dev/null
+++ b/docs/extension_guide/readme.md
@@ -0,0 +1,6 @@
+# 文档结构
+
+1. 基本介绍.
+2. hello world(第一步「连接」,引用英荔的工作)
+3. 积木说明
+4. 项目链接(参考Scratch wiki)
\ No newline at end of file
diff --git a/docs/extension_guide/robotic-arm.md b/docs/extension_guide/robotic-arm.md
new file mode 100644
index 0000000..64c38f5
--- /dev/null
+++ b/docs/extension_guide/robotic-arm.md
@@ -0,0 +1,110 @@
+# 小象机械臂
+
+## 介绍
+
+
+
+> 人人都可以学习玩耍的入门级协作机器人
+
+> myCobot的设计初衷是为了让对六自由度串联机械臂感兴趣的朋友,可以从0到1的了解、学习和操作机械臂,创造前所未有的机械臂使用体验与教学价值。
+
+---
+
+### 接管机械臂的思路
+
+由于小象机械臂内置了树莓派(用于驱动机械臂),我们将在树莓派里运行一个 [Adapter Node](https://adapter.codelab.club/dev_guide/Adapter-Node/),以便于用户可以在 CodeLab Scratch 中驱动机械臂。
+
+这个例子展示 Adapter Node 的一种典型用法: Adapter Node与Adapter可以不在同一个主机上。
+
+## hello world
+
+### 用户电脑配置
+假设用户使用自己的电脑编程(运行 Adapter 和 Scratch)
+
+在运行Adapter之前,设置[配置项](https://adapter.codelab.club/user_guide/settings/) `OPEN_MESSAGE_HUB = true`
+
+之后运行 CodeLab Adapter.
+
+### 树莓派配置
+在机械臂树莓派里安装 [codelab_adapter_client_python](https://github.com/CodeLabClub/codelab_adapter_client_python): `pip3 install codelab_adapter_client`
+
+创建 `node_mycobot.py` :
+
+```py
+import time
+from loguru import logger
+
+from pymycobot.mycobot import MyCobot
+from pymycobot.genre import Angle
+from pymycobot import PI_PORT, PI_BAUD # 当使用树莓派版本的mycobot时,可以引用这两个变量进行MyCobot初始化
+
+from codelab_adapter_client import AdapterNode
+
+# 初始化一个MyCobot对象
+mc = MyCobot(PI_PORT, PI_BAUD)
+
+class PythonKernelExtension(AdapterNode):
+
+ NODE_ID = "eim/mycobot"
+ HELP_URL = "http://adapter.codelab.club/"
+ WEIGHT = 95
+ VERSION = "0.1" # extension version
+ DESCRIPTION = "mycobot"
+
+ def __init__(self, **kwargs):
+ adapter_host = "192.168.31.164" # 运行Adapter的计算机IP
+ super().__init__(codelab_adapter_ip_address = adapter_host, **kwargs)
+ # self.PyHelper = PyHelper()
+
+ def run_python_code(self, code):
+ try:
+ # eval(expression, globals=None, locals=None)
+ output = eval(code, {"__builtins__": None}, {
+ # "PyHelper": self.PyHelper,
+ # "requests": requests,
+ "mc": mc,
+ "Angle": Angle
+ })
+ except Exception as e:
+ output = str(e)
+ return output
+
+ # @verify_token
+ def extension_message_handle(self, topic, payload):
+ logger.info(f'python code: {payload["content"]}')
+ python_code = payload["content"]
+ output = self.run_python_code(python_code)
+ try:
+ output = str(output) # 不要传递复杂结构
+ except Exception as e:
+ output = str(e)
+ payload["content"] = output
+ message = {"payload": payload}
+ self.publish(message)
+
+ def run(self):
+ "避免插件结束退出"
+ while self._running:
+ time.sleep(0.5)
+
+node = PythonKernelExtension()
+node.receive_loop_as_thread()
+node.run()
+```
+
+在树莓派里运行它。
+
+之后即可在 CodeLab Scratch 对机械臂编程。
+
+参考这个 [Demo](https://create.codelab.club/projects/12360/editor/)
+
+## 积木说明
+
+## 项目链接
+
+## FAQ
+
+
+## 参考
+* [mycobot-RPi](https://www.elephantrobotics.com/mycobot-RPi/)
+* [机械臂左右摆动](https://www.elephantrobotics.com/docs/myCobot/1-introduction/6-raspberry_mycobot/pymycobot/1-arm_swing.html)
\ No newline at end of file
diff --git a/docs/extension_guide/rpi_gpio.md b/docs/extension_guide/rpi_gpio.md
index b4c6251..e6a1461 100644
--- a/docs/extension_guide/rpi_gpio.md
+++ b/docs/extension_guide/rpi_gpio.md
@@ -35,12 +35,12 @@ Raspbian 默认已经预装 gpiozero 。
!!! 提醒
- 如果你手头没有LED, 你可以[使用命令行工具 gpio](https://blog.just4fun.site/post/iot/raspberrypi-install-and-config/#%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7) ,观察引脚输出变化。
+ 如果你手头没有LED, 你可以[使用命令行工具 gpio](https://wwj718.github.io/post/iot/raspberrypi-install-and-config/#%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7) ,观察引脚输出变化。
gpio用的mode是wPi,而gpiozero用的是BCM,所以pin17对应wpi的pin0

# 最后
-我们并不打算构建完备的积木组操控树莓派,树莓派高度灵活,难以完全积木化它的所有特性,那样不会提高可理解性。 我们希望用户灵活使用 Python 去增强 Scratch ,更好的扩展模式可以参考[Python eval kernel](https://adapter.codelab.club/extension_guide/extension_python_kernel/)。
+我们并不打算构建完备的积木组操控树莓派,树莓派高度灵活,难以完全积木化它的所有特性,那样不会提高可理解性。 我们希望用户灵活使用 Python 去增强 Scratch ,更好的扩展模式可以参考[Python eval kernel](/extension_guide/extension_python_kernel/)。
当然一些功能是可以积木化的,期待你来提交 PR。
@@ -49,5 +49,5 @@ Raspbian 默认已经预装 gpiozero 。
CodeLab Adapter的目标是**连接**。
# 参考
-- [gpio 测试工具](https://blog.just4fun.site/post/iot/raspberrypi-install-and-config/#%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7)
+- [gpio 测试工具](https://wwj718.github.io/post/iot/raspberrypi-install-and-config/#%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7)
- [wiringPi updated to 2.52 for the Raspberry Pi 4B](http://wiringpi.com/wiringpi-updated-to-2-52-for-the-raspberry-pi-4b/)
\ No newline at end of file
diff --git a/docs/extension_guide/simple_NLU.md b/docs/extension_guide/simple_NLU.md
index af430dd..d24cc8c 100644
--- a/docs/extension_guide/simple_NLU.md
+++ b/docs/extension_guide/simple_NLU.md
@@ -19,7 +19,7 @@ simple NLU 是一个简单的语义处理器,基于简单的规则匹配,可
## 插件说明
- 使用方式: 到[插件市场](/extension_guide/extension_market/)下载插件, 搜索 **NLU**
-- 插件类型: [Adapter Extension](https://adapter.codelab.club/dev_guide/helloworld/)
+- 插件类型: [Adapter Extension](/dev_guide/helloworld/)
- 插件源码: [extension_simple_NLU.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/extensions_v3/extension_simple_NLU.py)
## 依赖
@@ -32,7 +32,7 @@ simple NLU 是一个简单的语义处理器,基于简单的规则匹配,可
NLU 是一个语义解析器,语言文本的输入,是任意的。你可以使用 Scratch 内置的语音输入;也可以使用 Siri。
-如果你和视频 demo 一样,准备使用 Siri,需要先将 Siri 接入到 Adapter 中,参考[文档](https://adapter.codelab.club/extension_guide/siri/)。
+如果你和视频 demo 一样,准备使用 Siri,需要先将 Siri 接入到 Adapter 中,参考[文档](/extension_guide/siri/)。
## 步骤 1:打开 Codelab Adapter
diff --git a/docs/extension_guide/siri.md b/docs/extension_guide/siri.md
index 5e436e4..284de64 100644
--- a/docs/extension_guide/siri.md
+++ b/docs/extension_guide/siri.md
@@ -13,7 +13,7 @@ Hey siri
## 插件说明
- 使用方式: 到[插件市场](/extension_guide/extension_market/)下载插件, 搜索 **siri**
-- 插件类型: [Adapter Extension](https://adapter.codelab.club/dev_guide/helloworld/)
+- 插件类型: [Adapter Extension](/dev_guide/helloworld/)
- 插件源码: [extension_Siri.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/extensions_v3/extension_Siri.py)
## 依赖
@@ -64,4 +64,4 @@ Hey siri

# 原理说明
-* [CodeLab ❤ Siri](https://www.codelab.club/blog/codelab-like-siri/)
+* [CodeLab ❤ Siri](https://www-old.codelab.club/blog/codelab-like-siri/)
diff --git a/docs/extension_guide/sonicPi.md b/docs/extension_guide/sonicPi.md
index 82540a4..7d6780c 100644
--- a/docs/extension_guide/sonicPi.md
+++ b/docs/extension_guide/sonicPi.md
@@ -2,18 +2,19 @@

-# 插件说明
+## 介绍
-- 使用方式: 到[插件市场](/extension_guide/extension_market/)下载插件, 搜索 **sonicPi**
-- 插件类型: [Adapter Node](https://adapter.codelab.club/dev_guide/Adapter-Node/)
-- 插件源码: [node_sonicPi.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/nodes_v3/node_sonicPi.py)
-- 依赖库(python): `python -m pip install codelab_adapter_client python-sonic`
+Sonic Pi 是一个基于代码的音乐创作和表演工具。
-# 外部环境依赖
+拥有超过180万用户的多元社区。
+
+基于 Ruby 开发,最初设计用于支持学校的计算和音乐课程,由剑桥大学计算机实验室的 Sam Aaron 与树莓派基金会联合开发。可在主流操作系统中使用。
+
+### 外部环境依赖
需要下载[Sonic Pi](https://sonic-pi.net/)。
-# 开始使用
+## 开始使用
运行 Sonic Pi。
@@ -24,13 +25,33 @@
-# 进阶
+## 进阶
更多可用的指令,参考[python-sonic](https://github.com/gkvoelkl/python-sonic)。
如果你想深入了解[Sonic Pi](https://sonic-pi.net/),可以跟着 Sonic Pi 软件内置的文档学习。
-# 参考
+### 发送消息
+`>= 3.7.2` 的 Adapter 支持使用Scratch积木发送OSC消息,消息的参数(args)是一个 list(采用json语法,字符串使用 **双引号** )
+
+### Receiving OSC
+[Sonic Pi Receiving OSC](https://sonic-pi.net/tutorial.html#section-12-1)
+
+参考这个文档,你也可以基于[python-sonic](https://python-osc.readthedocs.io/en/latest/client.html#example)构建一个[自定义插件](/project_tutorial/eim_pt/#python)直接与Sonic Pi沟通。
+
+## FAQ
+
+### Linux用户
+Windows 和 Mac 用户开箱可用。Linux用户将自动安装依赖。
+
+Linux (Ubuntu 20.0)环境下安装完 Sonic Pi 后可能会出现程序无法启动的问题,这可能是因为 JACK 与 PulseAudio 在使用声卡上存在冲突造成的,参见 Sonic-Pi 仓库内这个 [issue 对该问题的讨论及最终的解决方法](https://github.com/sonic-pi-net/sonic-pi/issues/1025) 以及 [JACK 官方文档对相关问题的解释](https://jackaudio.org/faq/pulseaudio_and_jack.html)。具体的操作是:
+
+1. 在启动 Sonic-Pi 之前,先打开 QjackCtl(安装 Sonic-pi 时会自动安装),在 `Settings-Advanced`页面下,在 `Input Device` 中选择一个声卡,同时将 `Server Prefix` 修改为 `pasuspender -- /usr/bin/jackd`(我对此操作的理解是明确地为 JACK 选择一个声卡,同时暂停 PulseAudio 对它的可能占用。)
+2. 设置完成后保存,然后在控制页面上点击开始按钮,如果一切顺利终端内没有报错的话,这时再去运行 Sonic Pi 应该就会正常启动了。
+
+
+
+## 参考
- [Sonic Pi](https://sonic-pi.net/)
- [python-sonic](https://github.com/gkvoelkl/python-sonic)
diff --git a/docs/extension_guide/spheroRVR.md b/docs/extension_guide/spheroRVR.md
new file mode 100644
index 0000000..a96e5e7
--- /dev/null
+++ b/docs/extension_guide/spheroRVR.md
@@ -0,0 +1,58 @@
+# Sphero RVR
+
+
+!!! 提醒
+ 建议使用 APP 把固件升级到最新
+
+## 介绍
+RVR 是 Sphero 出品的一款教育机器人,内置多种传感器并支持丰富的硬件改装。此扩展可以实时控制通过蓝牙连接的 RVR。
+
+
+## Demo
+
+
+## 进阶
+### API
+使用 **广播** 积木调用 Python API: [Sphero Edu API](https://spherov2.readthedocs.io/en/latest/sphero_edu.html)
+
+## bug 记录
+只要 `import bleak` 就会出现这个问题.(lego mario也是)
+
+windows10 的某些版本([32bit 19041-SP0](https://item.m.jd.com/product/10026933866200.html?wxa_abtest=o&utm_source=iosapp&utm_medium=appshare&utm_campaign=t_335139774&utm_term=Wxfriends&ad_od=share&utm_user=plusmember&gx=RnFlx2ALOzTdndRJ-tE-G6S52g))认为该插件存在安全问题,导致adapter退出,参考
+
+* [How do I setup configuration when I use command line to build C#/.NET?
+](https://stackoverflow.com/questions/6469513/how-do-i-setup-configuration-when-i-use-command-line-to-build-c-net)
+* [loadFromRemoteSources](https://stackoverflow.com/questions/17615769/running-an-ironpython-script-from-python-sandbox-loadfromremotesources)
+
+造成bug的原因可能是依赖库造成的(因为调用系统蓝牙?)
+
+ps: 4.9.0 或许可用。
+
+
+
\ No newline at end of file
diff --git a/docs/extension_guide/stage.md b/docs/extension_guide/stage.md
index d53d2bb..a43fd8e 100644
--- a/docs/extension_guide/stage.md
+++ b/docs/extension_guide/stage.md
@@ -15,7 +15,7 @@ Adapter Stage 插件允许将舞台区的图像(舞台或者摄像头图像)
!!! 提醒
在 Adapter 3.2 之前,你需要自行从[插件市场](/extension_guide/extension_market/s)里下载 Stage 插件。 [源码地址](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/extensions_v3/extension_stage.py)
-运行 Stage 插件(当前插件的功能是把图像保存到 [Adapter Home 目录](https://adapter.codelab.club/user_guide/FAQ/#adapter)),你可以通过修改插件做其他事情。
+运行 Stage 插件(当前插件的功能是把图像保存到 [Adapter Home 目录](/user_guide/FAQ/#adapter)),你可以通过修改插件做其他事情。
如果你想使用 OpenCV 或 Tensorflow 处理图像,需要自定义[Adapter Node](/dev_guide/Adapter-Node/)
diff --git a/docs/extension_guide/sugar.md b/docs/extension_guide/sugar.md
new file mode 100644
index 0000000..4850849
--- /dev/null
+++ b/docs/extension_guide/sugar.md
@@ -0,0 +1,34 @@
+# Sugar
+
+
+
+构建 Sugar 插件的动机最初来自 @Lounsen 的想法: 接布尔量的 hat 积木。
+
+Sugar 插件是个实验室,试图为 Scratch 提供 **甜** 的语法糖。
+
+新的想法/需求,欢迎在[这个帖子](https://discuss.codelab.club/t/topic/169)下讨论
+
+# 积木介绍
+
+## 接布尔量的 hat 积木
+当情况**发生变化**,并且满足添加,才触发。类似 **当角色被点击** 积木。
+
+- [Demo](https://scratch-beta.codelab.club/?sb3url=https://adapter.codelab.club/sb3/Scratch-when-true.sb3)
+
+
+
+上述例子,表达的是: 如果 **我的变量** 一直 **<** 50,只向前移动一次.
+
+使用hat积木我们可以构造出与 Scratch 原生事件积木类似的东西:
+
+
+
+### 与linda积木配合使用
+
+
+hat 积木中只应包含 Scratch 原生积木,最好不要放入扩展积木。
+
+可能引起异常的原因还在进一步调查中。
+
+## Color积木
+参考 [Computer Colors](https://en.scratch-wiki.info/wiki/Computer_Colors)
\ No newline at end of file
diff --git a/docs/extension_guide/switch.md b/docs/extension_guide/switch.md
new file mode 100644
index 0000000..e002e6e
--- /dev/null
+++ b/docs/extension_guide/switch.md
@@ -0,0 +1,7 @@
+# Switch
+
+我们之前的演示案例是利用 Toy-Con Garage 做的。
+
+任天堂没有开放接口。
+
+我们通过捕获 Toy-Con Garage 的视觉输出做的连接。
\ No newline at end of file
diff --git a/docs/extension_guide/teachable_machine.md b/docs/extension_guide/teachable_machine.md
index 707a12d..2dcddbe 100644
--- a/docs/extension_guide/teachable_machine.md
+++ b/docs/extension_guide/teachable_machine.md
@@ -2,29 +2,29 @@
## 介绍
-[CodeLab Adapter 接入 Teachable Machine](https://www.codelab.club/blog/adapter-teachable-machine/)
-
-## 依赖
+[CodeLab Adapter 接入 Teachable Machine](https://www-old.codelab.club/blog/adapter-teachable-machine/)
{!utils/dependence.md!}
-安装 Chrome 浏览器插件:[Tampermonkey](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo)。
-点击安装 Tampermonkey 脚本 [Teachablemachine_Result.user.js](https://gist.github.com/wwj718/78402d0de9efb8d33742c8770056489c/raw/4e1373c3ce0d1c86af93310b42321251bee567b3/Teachablemachine_Result_fixed.user.js)。
+## hello world
+
+### 打开 Teachable Machine
-## 步骤 1:打开 Codelab Adapter
+Longan团队已经将Teachable Machine部署到国内: [Longan Teachable Machine](https://train.longan.link/), 并且内置支持CodeLab Adapter, 开箱可用!
-{!utils/open_adapter.md!}
+!!! 提醒
+ 海外用户参考文末操作
-## 步骤 2:打开 [Teachable Machine](https://teachablemachine.withgoogle.com/train)
+
-完成上述工作后,打开 [Teachable Machine](https://teachablemachine.withgoogle.com/train),页面应该会弹出提示:`connected!`,表示已经将 Teachable Machine 接入 CodeLab Adapter。
+表示已经将 Teachable Machine 接入 CodeLab Adapter 了。
接下来,可以开始你的 Teachable Machine 之旅途。 我们来展示一个例子。

-## 步骤 3:打开 [Codelab Scratch3](https://scratch-beta.codelab.club/)
+### 打开 [Codelab Scratch3](https://scratch-beta.codelab.club/)
{!utils/open_scratch.md!}
@@ -40,4 +40,19 @@
!!! 提醒
Teachable Machine 的网页不能在后台运行,需要和 Scratch 一起并列在桌面上,否则程序不会运行。
+## 积木说明
+暂无
+
+## 项目链接
+暂无
+
+## FAQ
+### 海外用户如何使用
+
+如果你是海外用户,建议使用 [Google 官方的 teachablemachine](https://teachablemachine.withgoogle.com/), 你需要搭配以下插件。
+
+安装 Chrome 浏览器插件:[Tampermonkey](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo)。
+
+点击安装 Tampermonkey 脚本 [Teachablemachine_Result.user.js](https://gist.githubusercontent.com/wwj718/78402d0de9efb8d33742c8770056489c/raw/2b99784ff8cf0d344e86e8c2b781b0babfc84c33/Teachablemachine_Result_fixed.user.js)。
+运行Adapter之后如何页面弹出,已连接Adapter,则说明一切正常
\ No newline at end of file
diff --git a/docs/extension_guide/tello.md b/docs/extension_guide/tello.md
index 1f8c983..baa7b33 100644
--- a/docs/extension_guide/tello.md
+++ b/docs/extension_guide/tello.md
@@ -1,5 +1,8 @@
# DJI Tello
+!!! tello
+ 请使用 [tello2.0](/extension_guide/tello/)
+
@@ -11,6 +14,7 @@
以下是在线版使用教程,离线版基本相似。
+
### 步骤 1:打开 [CodeLab Scratch](https://scratch-beta.codelab.club?adapter_host=127.0.0.1)
运行CodeLab Adapter, 确保在线平台与Adapte连接正常。
@@ -21,7 +25,7 @@

@@ -61,3 +65,21 @@
## DJI Tello x Switch Joy-Con
+
+# 改进
+目前 Tello 的插件都已开源,很久没更新,稳定性不高,大家可以一起改进它
+
+* [extension_tello](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/extensions_v3/extension_tello.py)
+* [scratch3_tello](https://github.com/CodeLabClub/scratch3_tello)
+
+!!! 提醒
+ 如果你希望做一些更复杂的事,建议直接使用社区里的 Python SDK与 设备交互,之后使用 [Adapter Node](/dev_guide/Adapter-Node/) 将其接入Adapter环境中。
+
+## Tello api 文档
+* [SDK 2.0](https://dl-cdn.ryzerobotics.com/downloads/Tello/Tello%20SDK%202.0%20User%20Guide.pdf)
+* [DJITelloPy](https://github.com/damiafuentes/DJITelloPy)
+
+* [multi_robot_drone_example](https://robomaster-dev.readthedocs.io/zh_CN/latest/python_sdk/multi_robot_drone_example.html)
\ No newline at end of file
diff --git a/docs/extension_guide/tello2.md b/docs/extension_guide/tello2.md
new file mode 100644
index 0000000..5994b04
--- /dev/null
+++ b/docs/extension_guide/tello2.md
@@ -0,0 +1,86 @@
+# Tello 2.0
+
+Tello 插件的 2.0 版本, 基于[DJITelloPy](https://github.com/damiafuentes/DJITelloPy)库。
+
+支持Tello、Tello Edu 和 Tello TT。
+
+!!! 提醒
+ Tello 会占用 wifi,导致电脑无法联网,请使用 CodeLab Adapter 的离线模式: [FAQ:离线使用](/user_guide/FAQ/#_6) (在`>=3.4.0`的版本中可用)。
+ 更好的方式可能是将Tello接入路由器上,或者使用USB无线网卡,避免电脑无法上网。
+
+以下是在线版使用教程,离线版基本相似。
+
+
+### 步骤 1:打开 [CodeLab Scratch](https://scratch-beta.codelab.club)
+运行CodeLab Adapter, 确保在线平台与Adapte连接正常。
+
+看到 [CodeLab Scratch](https://scratch-beta.codelab.club) 指示灯显示绿色,代表连接成功。
+
+
+
+
+
+### 步骤 2:连接 Tello
+
+将电脑连上 Tello 的 wifi 热点。(操作细节可以参考 Tello 说明书)
+
+### 步骤 3:开始使用
+
+选择 scratch3 中的 Tello2.0 插件.
+
+
+
+
+
+
+
+运行 Tello2.0 插件。
+
+
+
+之后依次点击 `连接tello` 、 `起飞`
+
+
+
+起飞吧!
+
+# 一些案例:
+
+## DJI Tello x Leap Motion
+
+
+
+## DJI Tello x Switch Labo
+
+
+
+## DJI Tello x Switch Joy-Con
+
+
+
+## 进阶
+你可以在 Tello 广播积木里调用 [api](https://djitellopy.readthedocs.io/en/latest/tello/)!形如: [tello.flip_left()](https://djitellopy.readthedocs.io/en/latest/tello/#djitellopy.tello.Tello.flip_left)
+
+利用API,你也可以与停机坪(机器视觉)交互([get_mission_pad_id](https://djitellopy.readthedocs.io/en/latest/tello/#djitellopy.tello.Tello.get_mission_pad_id)).API里有很多与停机坪相关的函数。
+
+如果你希望做一些更复杂的事,建议直接使用社区里的 [DJITelloPy](https://github.com/damiafuentes/DJITelloPy) 与 设备交互([api](https://djitellopy.readthedocs.io/en/latest/tello/)),之后使用 [Adapter Node](/dev_guide/Adapter-Node/) 将其接入Adapter环境中。
+
+## Tello api 文档
+* [DJITelloPy](https://github.com/damiafuentes/DJITelloPy)
+* [SDK 2.0](https://dl-cdn.ryzerobotics.com/downloads/Tello/Tello%20SDK%202.0%20User%20Guide.pdf)
+
+
+
+* [multi_robot_drone_example](https://robomaster-dev.readthedocs.io/zh_CN/latest/python_sdk/multi_robot_drone_example.html)
+
+
+
+# FAQ
+[如何排查 无法发现设备 的问题?](https://adapter.codelab.club/user_guide/FAQ/#_9)
diff --git a/docs/extension_guide/tello3.md b/docs/extension_guide/tello3.md
new file mode 100644
index 0000000..3314ddc
--- /dev/null
+++ b/docs/extension_guide/tello3.md
@@ -0,0 +1,5 @@
+# Tello 3.0
+
+支持在 scratch 中扫描连接。
+
+功能同 [tello2.0](https://adapter.codelab.club/extension_guide/tello2/)
\ No newline at end of file
diff --git a/docs/extension_guide/tello4.md b/docs/extension_guide/tello4.md
new file mode 100644
index 0000000..659ec8d
--- /dev/null
+++ b/docs/extension_guide/tello4.md
@@ -0,0 +1,77 @@
+# Tello 4.0
+
+Tello 插件的 4.0 版本, 基于 DJI 官方的 SDK: [RoboMaster SDK](https://robomaster-dev.readthedocs.io/zh_CN/latest/python_sdk/beginner_drone.html)库。
+
+能够充分利用设备的能力。
+
+相比于 [Tello 2.0 插件](/extension_guide/tello2/),4.0能够[控制 LED](https://robomaster-dev.readthedocs.io/zh_CN/latest/python_sdk/beginner_drone.html#led)
+
+# 使用说明
+目前该插件并未内置到 Adapter 中(因其复杂的打包依赖,而且跨平台兼容性不好)。
+
+我们目前将插件构建为 [Adapter Node](/dev_guide/Adapter-Node/),可以在Adapter外部以普通Python文件运行,一旦运行起来,与普通Adapter插件是一样的,能够与Adapter体系的所有事物交互。
+
+## Python环境
+首先你本地需要有 Python 环境(`Python>=3.6`)
+
+你可以到 [Python 官方](https://www.python.org/)下载,也可以使用 CodeLab放在[国内的版本(Python3.7)](https://www.codelab.club/blog/2020/08/20/tools#python)
+
+!!! 提醒
+ Mac 用户和 Linux 本地很可能内置了 Python3
+
+### 安装依赖
+```bash
+pip install robomaster codelab_adapter_client --upgrade
+```
+
+## 开始!
+
+!!! 提醒
+ Tello 会占用 wifi,导致电脑无法联网,请使用 CodeLab Adapter 的离线模式: [FAQ:离线使用](/user_guide/FAQ/#_6) (在`>=3.4.0`的版本中可用)。
+ 更好的方式可能是将Tello接入路由器上,或者使用USB无线网卡,避免电脑无法上网。
+
+
+### 步骤 1:打开 [CodeLab Scratch](https://scratch-beta.codelab.club)
+运行CodeLab Adapter, 确保在线平台与Adapte连接正常。
+
+看到 [CodeLab Scratch](https://scratch-beta.codelab.club) 指示灯显示绿色,代表连接成功。
+
+
+
+
+### 步骤 1:运行[node_tello3.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/nodes_v3/node_tello3.py)
+
+将 [node_tello3.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/nodes_v3/node_tello3.py) 插件下载到本地(随便放在一个文件夹里),在命令行中进入到这个文件夹,使用 `python node_tello3.py` 运行它。
+
+### 步骤 2:连接 Tello
+
+将电脑连上 Tello 的 wifi 热点。(操作细节可以参考 Tello 说明书)
+
+
+### 步骤 3: 起飞吧!
+
+选择 scratch3 中的 EIM 插件.
+
+
+
+以下是一个简单 demo:
+
+
+
+
+[tello3-demo](https://scratch-beta.codelab.club/?sb3url=https://adapter.codelab.club/sb3/Scratch-tello3.sb3)
+
+
+
+
+
+
+起飞吧!
+
+# 进阶
+
+更多API参考文档: [RoboMaster SDK](https://robomaster-dev.readthedocs.io/zh_CN/latest/python_sdk/beginner_drone.html)
\ No newline at end of file
diff --git a/docs/extension_guide/tensorflow-yolov4.md b/docs/extension_guide/tensorflow-yolov4.md
index d57f858..4d3ab15 100644
--- a/docs/extension_guide/tensorflow-yolov4.md
+++ b/docs/extension_guide/tensorflow-yolov4.md
@@ -1,8 +1,8 @@
# Tutorial
-[tensorflow-yolov4](https://github.com/hhk7734/tensorflow-yolov4), 请参考文档,安装响应依赖。
+[tensorflow-yolov4](https://github.com/hhk7734/tensorflow-yolov4), 请参考文档,安装相应依赖。
-接入方法参考[使用4 行 Pyhon 代码扩展 Scratch](https://blog.just4fun.site/post/%E5%B0%91%E5%84%BF%E7%BC%96%E7%A8%8B/4-line-python-code-as-scratch-ext/)
+接入方法参考[Python对象的连接器:EIM 插件](/project_tutorial/eim_pt/)
使用前请将[coco.names](https://github.com/hhk7734/tensorflow-yolov4/tree/master/test/dataset) 和 [yolov4-tiny.weights](https://drive.google.com/file/d/1GJwGiR7rizY_19c_czuLN8p31BwkhWY5/view?usp=sharing) 下载到对应目录。
diff --git a/docs/extension_guide/tensorflow.md b/docs/extension_guide/tensorflow.md
index a11e055..a43d1fc 100644
--- a/docs/extension_guide/tensorflow.md
+++ b/docs/extension_guide/tensorflow.md
@@ -1,2 +1,6 @@
# Tensorflow
-参考[运行在树莓派中的 codelab-adapter tensorflow 插件](https://blog.just4fun.site/adapter-tensorflow.html)。
+
+
+
+
+目前北京王府国际学校在使用的 codelab-adapter-4_4_1-AI-alpha 内置 Tensorflow, 细节参考[定制与分发](https://adapter.codelab.club/dev_guide/%E5%AE%9A%E5%88%B6%E4%B8%8E%E5%88%86%E5%8F%91/)
\ No newline at end of file
diff --git a/docs/extension_guide/tuio.md b/docs/extension_guide/tuio.md
new file mode 100644
index 0000000..6905ae6
--- /dev/null
+++ b/docs/extension_guide/tuio.md
@@ -0,0 +1,25 @@
+# 激光雷达
+
+激光雷达积木是一种在Scratch舞台上创建交互式触摸作品的积木块,将房间变成 Scratch 舞台区,你可以使用身体与Scratch进行互动!
+## 使用说明
+使用前,我们需要准备好激光雷达和投影仪并进行校准,校准之后的投影区域与舞台区域坐标保持一致,x、y坐标与Scratch中的x、y坐标概念相同,也是触点中最常用的数据。当你站在Scratch舞台上时,下面的积木块可以让角色跟随你的脚移动。
+
+积木中触点的概念类似于Scratch中的鼠标指针,与鼠标指针不同的是,激光雷达积木可以让Scratch舞台区具有多点触控的能力,并且都是由你编程实现的!使用时,一种好的做法是将触点的数据映射为Scratch中的一个角色,这个角色可以得到Scratch中其他积木块的能力,如碰撞侦测。如果不想在舞台上看到角色,可以让角色在舞台上透明。
+
+
+当触点数据包含了多个触点时,使用触点列表积木可以看到全部的触点,最新触点积木会看到最新加入的触点。
+如果只想关注触点列表中的某一个触点,我们也给出了根据下标获得触点的积木。
+
+
+触点拥有自己的生命,如果使用Scratch中的克隆来完成多点触控的代码,每一个触点都是一个克隆体,当触点不再处于活动状态时,最好删除这个克隆体。
+
+
+!!! tips
+ 多点触控的代码虽然可以复用,但写法并不是唯一的。想象一下你是作品的交互设计师,可以让代码去适应你独特的交互逻辑。
+
+
+
+## Demo视频
+[交互激光雷达](https://adapter.codelab.club/extension_guide/lidar/)
+## 更多作品
+[雷达互动作品工作室](https://create.codelab.club/studios/378)
diff --git a/docs/extension_guide/vector.md b/docs/extension_guide/vector.md
index dd5248b..8ec63f6 100644
--- a/docs/extension_guide/vector.md
+++ b/docs/extension_guide/vector.md
@@ -4,7 +4,6 @@
> Anki is a company whose products always seem to delight.
-We love [Cozmo](https://www.anki.com/en-us/cozmo), and [Vector](https://www.anki.com/en-us/vector).
[Codelab Adapter](https://adapterv2.codelab.club) is a software that connect Scratch 3.0 to the open-source hardware, IoT and AI.
@@ -38,6 +37,9 @@ windows user: `python -m pip install codelab_adapter_client --upgrade --user`
Follow Vector official tutorial: [Initial Setup](https://developer.anki.com/vector/docs/initial.html)
+!!! 提醒
+ 如果 Vector 的 IP发生了变化(诸如将Vector 带入新的 wifi 环境),不需要重新认证(python3 -m anki_vector.configure), 只需要修本地改配置文件即可(第一次认证留下的), 配置文件为: `~/.anki_vector/sdk_config.ini`
+
If the following code (`hello_world.py`) runs smoothly, go to the next step.
```python
@@ -65,7 +67,7 @@ if __name__ == "__main__":
### Download Codelab Adapter
-Download Codelab Adapter
+Download Codelab Adapter
run it
diff --git a/docs/extension_guide/wechat.md b/docs/extension_guide/wechat.md
index df861d8..52cdff9 100644
--- a/docs/extension_guide/wechat.md
+++ b/docs/extension_guide/wechat.md
@@ -1,5 +1,11 @@
# Tutorial
+## 介绍
+该插件使用 [ItChat](https://github.com/littlecodersh/ItChat) 接入微信, ItChat 基于网页微信接口。
+
+!!! 提醒
+ 大量用户无法使用网页微信。可以通过扫码:[网页微信](https://wx.qq.com/) 看看自己能否登陆腾讯官方的网页微信,如果你无法登陆网页微信(腾讯对你的账号权限做了限制),则无法使用该插件。
+
## 依赖
{!utils/dependence.md!}
diff --git a/docs/extension_guide/yeelight.md b/docs/extension_guide/yeelight.md
new file mode 100644
index 0000000..235147f
--- /dev/null
+++ b/docs/extension_guide/yeelight.md
@@ -0,0 +1,16 @@
+# yeelight
+
+使用 Adapter 直接驱动 yeelight,不需要 Home Assistant/LonganHub
+
+插件源码: [node_yeelight.py](https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/nodes_v3/node_yeelight.py)
+
+# Scratch Demo
+
+* [Scratch EIM demo](https://scratch-beta.codelab.club/?sb3url=https://adapter.codelab.club/sb3/yeelight-eim-demo.sb3)
+ * [社区版](https://create.codelab.club/projects/8097/)
+
+# 文档
+
+[yeelight](https://yeelight.readthedocs.io/en/latest/)
+
+yeelight 速率限制为每分钟60个。如果你想解除限制,则需要使用: Music mode : `bulb.start_music()`
\ No newline at end of file
diff --git a/docs/extensions_nodes_mirrors/extension_Aqara_scene.py b/docs/extensions_nodes_mirrors/extension_Aqara_scene.py
index aa3ce93..cdc7ed3 100644
--- a/docs/extensions_nodes_mirrors/extension_Aqara_scene.py
+++ b/docs/extensions_nodes_mirrors/extension_Aqara_scene.py
@@ -1,5 +1,4 @@
import hashlib
-import subprocess
import sys
import time
import webbrowser
@@ -7,7 +6,6 @@
import requests
from codelab_adapter.core_extension import Extension
-from codelab_adapter.settings import TOKEN
from codelab_adapter.utils import (open_path_in_system_file_manager,
verify_token)
@@ -80,7 +78,6 @@ def run_python_code(self, code):
output = e
return output
- @verify_token
def extension_message_handle(self, topic, payload):
'''
所有可能运行代码的地方,都加上验证,确认payload中代码风险和token
diff --git a/docs/extensions_nodes_mirrors/extension_RoboMaster.py b/docs/extensions_nodes_mirrors/extension_RoboMaster.py
index 6522452..ebdbef2 100644
--- a/docs/extensions_nodes_mirrors/extension_RoboMaster.py
+++ b/docs/extensions_nodes_mirrors/extension_RoboMaster.py
@@ -5,7 +5,6 @@
from codelab_adapter.core_extension import Extension
-
class RoboMasterExtension(Extension):
NODE_ID = "eim/extension_RoboMaster"
HELP_URL = "http://adapter.codelab.club/extension_guide/RoboMaster/"
@@ -25,11 +24,11 @@ def get_robot_ip(self):
try:
ip_sock.settimeout(3)
# wait...
- ip_str = ip_sock.recvfrom(1024) # block
+ ip_str = ip_sock.recvfrom(1024) # block
host = ip_str[-1][0]
return host
except Exception as e:
- self.pub_notification(str(e),type="ERROR")
+ self.pub_notification(str(e), type="ERROR")
raise e
def create_command_socket(self):
@@ -52,10 +51,10 @@ def run(self):
# 默认建立连接
connect_msg = "command;"
command_socket.send(connect_msg.encode('utf-8'))
- buf = command_socket.recv(1024) # todo: timeout
+ buf = command_socket.recv(1024) # todo: timeout
# todo 开启事件上报, 默认开启,使用一个新的socket线程,接收事件。使用bucket token
self.logger.info(f"connect: {buf}")
- self.pub_notification("Device(RoboMaster) Connected!", type="SUCCESS")
+ self.pub_notification("RoboMasterEP 已连接", type="SUCCESS")
while self._running:
# wait for the command for the client(scratch/web app)
time.sleep(0.05)
@@ -63,11 +62,11 @@ def run(self):
payload = self.q.get()
msg = payload["content"]
# send the command to the robot
- command_socket.send(msg.encode('utf-8')) # todo: noblock
+ command_socket.send(msg.encode('utf-8')) # todo: noblock
try:
# wait for the reply
- buf = command_socket.recv(1024) # todo: timeout
+ buf = command_socket.recv(1024) # todo: timeout
output = buf.decode('utf-8')
payload["content"] = str(output)
message = {"payload": payload}
diff --git a/docs/extensions_nodes_mirrors/extension_Siri.py b/docs/extensions_nodes_mirrors/extension_Siri.py
index 4304307..646c1bf 100644
--- a/docs/extensions_nodes_mirrors/extension_Siri.py
+++ b/docs/extensions_nodes_mirrors/extension_Siri.py
@@ -9,12 +9,24 @@
import queue
import requests
-import bottle
-from bottle import route, run, template, view, request
from codelab_adapter.core_extension import Extension
-from codelab_adapter.utils import threaded, get_local_ip
-from codelab_adapter_client.utils import get_adapter_home_path
+from codelab_adapter.utils import threaded, is_win
+from codelab_adapter_client.utils import get_adapter_home_path, get_local_ip
+
+# windows pythonw
+if is_win():
+ import io
+ from contextlib import redirect_stdout, redirect_stderr
+ stdout = io.StringIO()
+ stderr = io.StringIO()
+ with redirect_stdout(stdout), redirect_stderr(stderr):
+ import bottle # flask fix windows sys.stdout.write, itchat(node)
+ from bottle import route, run, template, view, request
+else:
+ import bottle # flask fix windows sys.stdout.write, itchat(node)
+ from bottle import route, run, template, view, request
+
# html 文件所在目录
PORT = 18081
message_queue = queue.Queue()
@@ -84,7 +96,7 @@ def __init__(self, **kwargs):
@threaded
def _run_webserver_as_thread(self):
- try:
+ try:
run(host='0.0.0.0', port=self.port)
except OSError as e:
self.logger.warning(str(e))
diff --git a/docs/extensions_nodes_mirrors/extension_arduino_uno.py b/docs/extensions_nodes_mirrors/extension_arduino_uno.py
index ad2ae56..c622f8c 100644
--- a/docs/extensions_nodes_mirrors/extension_arduino_uno.py
+++ b/docs/extensions_nodes_mirrors/extension_arduino_uno.py
@@ -454,7 +454,7 @@ def run(self):
try:
self.event_loop.create_task(
- self.pub_notification(f'Arduino UNO Connected!',
+ self.pub_notification('Arduino UNO 已连接',
type="SUCCESS"))
self.event_loop.run_until_complete(self.main())
self.logger.debug("arduino thread end")
diff --git a/docs/extensions_nodes_mirrors/extension_http_eim.py b/docs/extensions_nodes_mirrors/extension_http_eim.py
index 7a17a94..a8432f2 100644
--- a/docs/extensions_nodes_mirrors/extension_http_eim.py
+++ b/docs/extensions_nodes_mirrors/extension_http_eim.py
@@ -13,8 +13,8 @@
from bottle import route, run, template, view, request
from codelab_adapter.core_extension import Extension
-from codelab_adapter.utils import threaded, get_local_ip
-from codelab_adapter_client.utils import get_adapter_home_path
+from codelab_adapter.utils import threaded
+from codelab_adapter_client.utils import get_adapter_home_path, get_local_ip
# html 文件所在目录
PORT = 18082
diff --git a/docs/extensions_nodes_mirrors/extension_jupyterlab.py b/docs/extensions_nodes_mirrors/extension_jupyterlab.py
index 65a281f..79801ed 100644
--- a/docs/extensions_nodes_mirrors/extension_jupyterlab.py
+++ b/docs/extensions_nodes_mirrors/extension_jupyterlab.py
@@ -1,16 +1,13 @@
-import os
-import pathlib
-import platform
-import subprocess
-import sys
import time
-import signal
+
+from codelab_adapter.jupyterlab_manage import jupyterlabProxy
from codelab_adapter.core_extension import Extension
-from codelab_adapter.launcher import launch_proc # just like subprocess.Popen, but better cross-platform support
from codelab_adapter.local_env import EnvManage
-from codelab_adapter.utils import get_html_message_for_no_local_python
-from codelab_adapter_client.utils import get_python3_path, install_requirement, get_adapter_home_path
+from codelab_adapter.utils import get_python3_path, get_html_message_for_no_local_python, is_linux
+from codelab_adapter_client.utils import install_requirement, get_adapter_home_path
+
+# https://github.com/jupyterlab/jupyterlab/blob/36037151f0ddddf84715d3e693f3f02dd483960d/jupyterlab/labapp.py#L409 Jupyter 的参数
class JupyterlabExtension(Extension):
@@ -31,55 +28,45 @@ def __init__(self, **kwargs):
self.jupyter_proc = None
def _install_requirement(self): # to install jupyterlab
- self.pub_notification(f"jupyterlab is being installed...")
+ self.pub_notification("正在安装 JupyterLab...")
output = install_requirement(self.REQUIREMENTS)
if output == 0:
- self.pub_notification("jupyterlab installed!")
+ self.pub_notification("JupyterLab 安装完成")
self.env_manage.set_env(None) # update env
- def run_jupyterlab(self):
- cmd = [
- self.python_path, "-m", "jupyterlab", "--notebook-dir",
- str(self.adapter_home_path)
- ]
- try:
- self.jupyter_proc = launch_proc(
- cmd,
- # shell=True,
- logger=self.logger,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE) # work with windows
- except Exception as e:
- self.pub_notification(str(e), type="ERROR")
-
def run(self):
env = self.env_manage.get_env() # the local env, todo: docs
- if not env["local Python3"].get("path"):
- html_message = get_html_message_for_no_local_python()
- self.pub_html_notification(html_message)
- return
+ # linux (lite version)
+ if is_linux():
+ '''
+ if not env["local Python3"].get("path"):
+ html_message = get_html_message_for_no_local_python()
+ self.pub_html_notification(html_message)
+ return
+ '''
- if not env["treasure box"].get("jupyterlab"):
- self._install_requirement()
- self.pub_notification("ready to open jupyterlab...")
- self.run_jupyterlab()
+ # if not env["treasure box"].get("jupyterlab"):
+ pass
+ # self._install_requirement()
+ # self.run_jupyterlab()
+ self.pub_notification("正在启动 jupyterlab...")
+ self.jupyter_proc = jupyterlabProxy().run_jupyterlab()
while self._running:
time.sleep(1)
- def terminate(self):
+ def terminate(self, **kwargs):
'''
stop thread
'''
if self._running:
- self.pub_notification("try to stop jupyterlab")
+ self.pub_notification("正在停止 JupyterLab")
if self.jupyter_proc:
# fuck Windows! 🖕️
- self.jupyter_proc.kill()
+ self.jupyter_proc.terminate()
# os.killpg(os.getpgid(self.jupyter_proc.pid), signal.SIGTERM)
- self.jupyter_proc.wait()
+ self.jupyter_proc.join()
# 启动 node 的东西 ,使用 psutil? delegator.py?
- self.pub_notification("jupyterlab stopped")
+ self.pub_notification("JupyterLab 已停止")
time.sleep(0.1)
super().terminate()
diff --git a/docs/extensions_nodes_mirrors/extension_microbit_radio.py b/docs/extensions_nodes_mirrors/extension_microbit_radio.py
index b18ac7b..9c616a4 100644
--- a/docs/extensions_nodes_mirrors/extension_microbit_radio.py
+++ b/docs/extensions_nodes_mirrors/extension_microbit_radio.py
@@ -1,12 +1,177 @@
import time
+import serial
+import json
+from collections import deque
+import threading
+from codelab_adapter.utils import list_microbit, flash_makecode_file
+from codelab_adapter_client.utils import get_adapter_home_path, threaded
+
+# from extension_usb_microbit import MicrobitHelper # extensions/extension_usb_microbit.py
from codelab_adapter.core_extension import Extension
-from codelab_adapter.microbit_helper import MicrobitRadioHelper
+from codelab_adapter_client.thing import AdapterThing
+
+# from codelab_adapter.microbit_helper import MicrobitRadioHelper
'''
-todo 错误信息报告 通知
+todo
+ serial 锁
+ 队列/缓冲
+ 数字
+
+bug
+ 为何第一次连接 读不到versions
+
+fifo = deque()
+fifo.append(1) # deque([1])
+fifo.append(2) # deque([1, 2])
+2 in fifo # true
+fifo.popleft() # deque([2])
'''
+class ThingProxy(AdapterThing):
+ def __init__(self, node_instance):
+ super().__init__(thing_name="micro:bit-radio",
+ node_instance=node_instance)
+ # 定长 https://stackoverflow.com/questions/1931589/python-datatype-for-a-fixed-length-fifo
+ # The append() and popleft() methods are both atomic.
+ # self.write_fifo_queue = deque() # to microbit
+ # self.read_fifo_queue = deque() # from microbit
+ self.lock = threading.Lock()
+
+ def list(self, timeout=5) -> list:
+ microbit_ports = list_microbit() # return
+ if not microbit_ports:
+ self.node_instance.logger.error("未发现 micro:bit")
+ self.node_instance.pub_notification("未发现 micro:bit", type="ERROR")
+
+ return [str(i[0]) for i in microbit_ports]
+
+ # def connect(self, ip, timeout=5):
+ def connect(self, id, **kwargs):
+ self.port = id
+ # self.node_instance.logger.debug(f"args: {kwargs}")
+ if not kwargs.get("baudrate", None):
+ kwargs["baudrate"] = 115200
+ # if not kwargs.get("timeout", None):
+ timeout = kwargs["timeout"] # client输入
+ kwargs["timeout"] = 0.5 # 串口超时时间
+ ser = serial.Serial(self.port, **kwargs)
+
+ firmware_path = str(get_adapter_home_path() / "src" /
+ "makecode_radio_adapter.hex")
+ t1 = time.time()
+ # time.sleep(0.1)
+ while self.node_instance._running:
+ if time.time() - t1 >= timeout / 2:
+ # 超时没收到version信息, 可能是第一次烧录
+ self.node_instance.logger.error("get version timeout")
+ ser.close()
+ self.node_instance.pub_notification("正在刷入固件...", type="INFO")
+ flash_makecode_file(firmware_path)
+ time.sleep(10)
+ return
+ ser.write(b"version\n") # 没写进去,microbit没有反应
+ data = ser.readline() # timeout
+ if data:
+ self.node_instance.logger.debug(f"version query reply: {data}")
+ data = data.decode().strip()
+ if data.startswith("version_"):
+ version = data.split('version_')[-1]
+ self.node_instance.logger.debug(
+ f"makecode radio firmware -> {version}")
+ if version >= "0.5": # todo 0.5 set radio_03 其他不要动,固件变了
+ self.is_connected = True # 连接成功
+ self.thing = ser
+ if self.thing.name:
+ self.node_instance.pub_notification(
+ "micro:bit 已连接", type="SUCCESS")
+ self.run_task_forever()
+ return "ok"
+ else:
+ self.thing.close()
+ self.node_instance.pub_notification("正在刷入固件...",
+ type="INFO")
+ flash_makecode_file(firmware_path)
+ time.sleep(10)
+ # self.node_instance.pub_notification(self.flash_finished_notification, type="SUCCESS")
+ return # 只有串口数据是 version_xx才退出循环
+
+ def status(self, **kwargs) -> bool:
+ pass
+
+ def disconnect(self):
+ # 不要try,暴露问题
+ self.is_connected = False # todo 不需要这个,使用thing 是否存在即可
+ try:
+ self.thing.close()
+ except Exception:
+ pass
+ self.thing = None
+ self.node_instance.pub_notification(f'{self.node_instance.NODE_ID} 已断开', type="WARNING") # 切断插件?
+
+ # 业务
+ def write(self, content):
+ if self.is_connected:
+ self.lock.acquire() # todo queue? 好像某些 windows 会导致 microbit死掉 好像是发的东西不对,在 microbit 观察下
+ self.thing.write(content.encode('utf-8')) # todo 线程安全
+ # time.sleep(0.1) # 避免遗漏消息
+ self.thing.flush()
+ # 前端使用等待的
+ self.lock.release()
+ return "ok"
+
+
+ def uart_helper(self):
+ '''
+ 后台任务
+ 放入缓冲区
+ 1000个长度
+ 写入任务
+ 在同个循环里,避免跨线程
+ 使用queue跨线程
+
+ return
+ microbit output
+ '''
+ if self.is_connected:
+ try:
+ # 写入队列和读出队列
+ # write from queue
+ data = self.thing.readline() # timeout?
+ self.node_instance.logger.debug(f"readline: {data}")
+ if data:
+ data_str = data.decode()
+ return data_str.strip()
+ except Exception as e:
+ # self.is_connected = False
+ # self.thing = None
+ self.node_instance.logger.error(e)
+ self.node_instance.pub_notification(str(e), type="ERROR")
+ time.sleep(0.1) # 提示设备未连接
+ # 强行拔掉后,自行退出
+ self.node_instance.terminate() # 需要插件退出吗?
+
+ @threaded
+ def run_task_forever(self):
+ while self.node_instance._running:
+ if self.is_connected:
+ # microbit node -> microbit adapter
+ response_from_microbit = self.uart_helper()
+ if response_from_microbit:
+ if response_from_microbit.startswith('version_'):
+ # version信息, 状态机 todo
+ pass
+ else:
+ message = self.node_instance.message_template()
+ message["payload"]["content"] = response_from_microbit
+ # 来自radio的消息,与控制消息区分开
+ message["payload"]["message_type"] = 'radio_message'
+ self.node_instance.publish(message)
+ else:
+ break
+
+
class MicrobitRadioProxy(Extension):
'''
use TokenBucket to limit message rate(pub) https://github.com/CodeLabClub/codelab_adapter_client_python/blob/master/codelab_adapter_client/utils.py#L25
@@ -15,22 +180,29 @@ class MicrobitRadioProxy(Extension):
NODE_ID = "eim/extension_microbit_radio"
HELP_URL = "http://adapter.codelab.club/extension_guide/microbit_radio/"
WEIGHT = 98
- VERSION = "1.1" # 简化makecode对字符串的处理,移除\r
+ VERSION = "2.0.0" # 简化makecode对字符串的处理,移除\r
DESCRIPTION = "Microbit radio 信号中转站"
def __init__(self, **kwargs): # kwargs 接受启动参数
super().__init__(
- bucket_token=200, # 默认是100条 hub模式消息量大
+ bucket_token=100, # 默认是100条 hub模式消息量大
bucket_fill_rate=100,
**kwargs)
- self.microbitHelper = MicrobitRadioHelper(self)
+ self.thing = ThingProxy(self)
+ # self.microbitHelper = MicrobitRadioHelper(self)
def run_python_code(self, code):
# fork from python extension
try:
- output = eval(code, {"__builtins__": None}, {
- "microbitHelper": self.microbitHelper,
- })
+ output = eval(
+ code,
+ {"__builtins__": None},
+ {
+ "thing": self.thing, # 直接调用方法
+ "connect": self.thing.connect,
+ "disconnect": self.thing.disconnect,
+ "list": self.thing.list,
+ })
except Exception as e:
output = str(e)
# 也作为提醒
@@ -39,32 +211,29 @@ def run_python_code(self, code):
def extension_message_handle(self, topic, payload):
'''
- test: codelab-message-pub -j '{"topic":"scratch/extensions/command","payload":{"node_id":"eim/extension_microbit_radio", "content":"microbitHelper.write(\"c\")"}}'
+ test: codelab-message-pub -j '{"topic":"scratch/extensions/command","payload":{"node_id":"eim/extension_microbit_radio", "content":"thing.write(\"c\")"}}'
'''
self.logger.info(f'python code: {payload["content"]}')
- message_id = payload.get("message_id")
+ # message_id = payload.get("message_id")
python_code = payload["content"]
- if "microbitHelper" in python_code:
- output = self.run_python_code(python_code)
- payload["content"] = output
- message = {"payload": payload} # 无论是否有message_id都返回
- self.publish(message)
+ output = self.run_python_code(python_code)
+ try:
+ output = json.dumps(output)
+ except Exception:
+ output = str(output)
+ payload["content"] = output
+ message = {"payload": payload}
+ self.publish(message)
def run(self):
- while self._running:
- if self.microbitHelper.ser:
- # node -> microbit adapter
- response_from_microbit = self.microbitHelper.get_response_from_microbit(
- )
- # print(response_from_microbit)
- if response_from_microbit:
- print(response_from_microbit)
- message = self.message_template()
- message["payload"]["content"] = response_from_microbit
- self.publish(message)
- else:
- time.sleep(0.5)
- # 广播不在线
+ time.sleep(0.1)
+
+ def terminate(self, **kwargs):
+ try:
+ self.thing.disconnect()
+ except Exception:
+ pass
+ super().terminate(**kwargs)
export = MicrobitRadioProxy
diff --git a/docs/extensions_nodes_mirrors/extension_mqtt_adapter.py b/docs/extensions_nodes_mirrors/extension_mqtt_adapter.py
index 99d4f4f..fb195e2 100644
--- a/docs/extensions_nodes_mirrors/extension_mqtt_adapter.py
+++ b/docs/extensions_nodes_mirrors/extension_mqtt_adapter.py
@@ -45,11 +45,6 @@ def __init__(self, **kwargs):
self.client.connect(self.mqtt_addr, self.mqtt_port, 60)
self.client.loop_start() # as thread
- def exit_message_handle(self, topic, payload):
- # stop mqtt client
- self.client.loop_stop()
- self.terminate()
-
def extension_message_handle(self, topic, payload):
'''
scratch eim -> mqtt
@@ -91,5 +86,8 @@ def run(self):
# to publish mqtt message
time.sleep(0.1)
+ def terminate(self, **kwargs):
+ self.client.loop_stop()
+ super().terminate(**kwargs)
export = MqttAdapterExtension
\ No newline at end of file
diff --git a/docs/extensions_nodes_mirrors/extension_python.py b/docs/extensions_nodes_mirrors/extension_python.py
index ea15e57..8c6ab9e 100644
--- a/docs/extensions_nodes_mirrors/extension_python.py
+++ b/docs/extensions_nodes_mirrors/extension_python.py
@@ -1,10 +1,3 @@
-import sys
-import time
-import subprocess
-import webbrowser
-from codelab_adapter.core_extension import Extension
-from codelab_adapter.utils import verify_token, open_path_in_system_file_manager
-from codelab_adapter.settings import TOKEN
'''
当前插件只允许运行表达式
如果你希望执行任意python代码,请使用: https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/extensions_v2/extension_python_kernel_exec.py,注意风险
@@ -12,10 +5,16 @@
也可以在Scratch EIM插件中运行Python代码
'''
+import time
+import webbrowser
+import requests
+from codelab_adapter.core_extension import Extension
+from codelab_adapter.utils import verify_token, open_path_in_system_file_manager
+
class PyHelper:
- def open_url(https://github.com/self,%20url):
- webbrowser.open(url)
+ def open_url(https://github.com/self,%20url,%20**kwargs):
+ webbrowser.open(url, **kwargs)
def open(self, path):
open_path_in_system_file_manager(path)
@@ -29,7 +28,7 @@ class PythonKernelExtension(Extension):
NODE_ID = "eim/extension_python"
HELP_URL = "http://adapter.codelab.club/extension_guide/extension_python_kernel/"
WEIGHT = 95
- VERSION = "1.0" # extension version
+ VERSION = "1.1" # extension version
DESCRIPTION = "Python eval"
def __init__(self, **kwargs):
@@ -37,34 +36,27 @@ def __init__(self, **kwargs):
self.PyHelper = PyHelper()
def run_python_code(self, code):
- '''
- mode
- 1 exec
- 2 eval
- 3 pass
- '''
try:
- # 出于安全考虑, 放弃使用exec,如果需要,可以自行下载exec版本
# eval(expression, globals=None, locals=None)
- # 如果只是调用(插件指责)可以使用json-rpc
output = eval(code, {"__builtins__": None}, {
"PyHelper": self.PyHelper,
+ "requests": requests,
})
except Exception as e:
- output = e
+ output = str(e)
return output
- @verify_token
+ # @verify_token
def extension_message_handle(self, topic, payload):
- '''
- 所有可能运行代码的地方,都加上验证,确认payload中代码风险和token
- '''
self.logger.info(f'python code: {payload["content"]}')
- message_id = payload.get("message_id")
python_code = payload["content"]
output = self.run_python_code(python_code)
- payload["content"] = str(output)
- message = {"payload": payload} # 无论是否有message_id都返回
+ try:
+ output = str(output) # 不要传递复杂结构
+ except Exception as e:
+ output = str(e)
+ payload["content"] = output
+ message = {"payload": payload}
self.publish(message)
def run(self):
diff --git a/docs/extensions_nodes_mirrors/extension_socket_server.py b/docs/extensions_nodes_mirrors/extension_socket_server.py
new file mode 100644
index 0000000..ebedc20
--- /dev/null
+++ b/docs/extensions_nodes_mirrors/extension_socket_server.py
@@ -0,0 +1,67 @@
+import time
+import re
+from loguru import logger
+from codelab_adapter_client.utils import send_simple_message
+from codelab_adapter.core_extension import Extension
+from socketserver import BaseRequestHandler, TCPServer
+
+
+class ScratchEchoHandler(BaseRequestHandler):
+
+ def handle(self):
+ print('Got connection from', self.client_address)
+ while True:
+ msg = self.request.recv(1024)
+ print(f'msg:${msg}')
+ if not msg:
+ break
+ else:
+ logger.debug(f'socket request message: {msg}')
+ # to adpaters
+ message_decode = msg.decode()
+ if "broadcast" in message_decode:
+ content = re.search(r'\"(.*?)\"', message_decode.split("broadcast")[-1]).groups()[0]
+ send_simple_message(content)
+ # if content == "exit":
+ # self.server.shutdown()
+ # self.server.server_close()
+ self.request.send(msg)
+
+
+class SocketServerExtension(Extension):
+ '''
+ Socket Server
+ 最初用于实现与 Etoys 互操作: https://blog.just4fun.site/post/%E5%B0%91%E5%84%BF%E7%BC%96%E7%A8%8B/etoys-learning-note/
+
+ socket client -> Scratch
+ '''
+ NODE_ID = "eim/extension_socket_server"
+ HELP_URL = "https://adapter.codelab.club/extension_guide/socket_server/" # Documentation page for the project
+ VERSION = "1.0" # extension version
+ DESCRIPTION = "Socket Server"
+ ICON_URL = ""
+ REQUIRES_ADAPTER = "" # ">= 3.2.0"
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ self.port = 42001
+ self.address = '127.0.0.1'
+ self.socket_server = TCPServer((self.address,self.port), ScratchEchoHandler)
+
+
+ def extension_message_handle(self, topic, payload):
+ pass
+
+ def run(self):
+ # 高频消息使用 osc
+ self.socket_server.serve_forever()
+
+ def terminate(self, **kwargs):
+ self.logger.debug('terminate!')
+ self.socket_server.shutdown() # 无法外部 shutdown
+ self.socket_server.server_close()
+ super().terminate(**kwargs)
+
+
+
+export = SocketServerExtension
\ No newline at end of file
diff --git a/docs/extensions_nodes_mirrors/extension_tello.py b/docs/extensions_nodes_mirrors/extension_tello.py
index 75cf376..3071080 100644
--- a/docs/extensions_nodes_mirrors/extension_tello.py
+++ b/docs/extensions_nodes_mirrors/extension_tello.py
@@ -7,6 +7,8 @@
# import numpy as np
# import libh264decoder
+# todo: 只考虑简单控制
+# 基于 https://github.com/hanyazou/TelloPy
class Tello:
"""Wrapper class to interact with the Tello drone."""
diff --git a/docs/extensions_nodes_mirrors/extension_uart_adapter.py b/docs/extensions_nodes_mirrors/extension_uart_adapter.py
index 64987bf..3311627 100644
--- a/docs/extensions_nodes_mirrors/extension_uart_adapter.py
+++ b/docs/extensions_nodes_mirrors/extension_uart_adapter.py
@@ -38,10 +38,10 @@ def connect(self, port, **kwargs):
def disconnect(self, **kwargs):
if self.ser:
self.ser.close()
- self.extensionInstance.pub_notification("device disconnected!")
+ self.extensionInstance.pub_notification("设备已断开")
return "ok"
else:
- self.extensionInstance.pub_notification("Please connect the device!", type="ERROR")
+ self.extensionInstance.pub_notification("未发现可用设备", type="ERROR")
def list_ports(self):
@@ -69,7 +69,7 @@ def write(self, content):
self.extensionInstance.terminate()
else:
self.extensionInstance.pub_notification(
- "Please connect the device!", type="ERROR")
+ "未发现可用设备", type="ERROR")
self.extensionInstance.terminate()
def readline(self):
@@ -85,7 +85,7 @@ def readline(self):
self.extensionInstance.terminate()
else:
self.extensionInstance.pub_notification(
- "Please connect the device!", type="ERROR")
+ "未发现可用设备", type="ERROR")
self.extensionInstance.terminate()
diff --git a/docs/extensions_nodes_mirrors/extension_usb_microbit.py b/docs/extensions_nodes_mirrors/extension_usb_microbit.py
index 724ef81..f2f4bde 100644
--- a/docs/extensions_nodes_mirrors/extension_usb_microbit.py
+++ b/docs/extensions_nodes_mirrors/extension_usb_microbit.py
@@ -1,18 +1,214 @@
-import queue
import time
+import serial
+import json
-from codelab_adapter.microbit_helper import UsbMicrobitHelper
+from codelab_adapter.utils import list_microbit, flash_makecode_file
+from codelab_adapter_client.utils import get_adapter_home_path, threaded
+
+# from extension_usb_microbit import MicrobitHelper # extensions/extension_usb_microbit.py
from codelab_adapter.core_extension import Extension
+from codelab_adapter_client.thing import AdapterThing
+import threading
+
+# from codelab_adapter.microbit_helper import MicrobitRadioHelper
'''
-todo:
- 使用makecode构建固件
- command
- query
- event
+todo
+ serial 锁
+ 队列/缓冲
+ 数字
+
+bug
+ 为何第一次连接 读不到versions
+
+fifo = deque()
+fifo.append(1) # deque([1])
+fifo.append(2) # deque([1, 2])
+2 in fifo # true
+fifo.popleft() # deque([2])
'''
-class UsbMicrobitProxy(Extension):
+class ThingProxy(AdapterThing):
+ def __init__(self, node_instance):
+ super().__init__(thing_name="USB micro:bit",
+ node_instance=node_instance)
+ # 定长 https://stackoverflow.com/questions/1931589/python-datatype-for-a-fixed-length-fifo
+ # The append() and popleft() methods are both atomic.
+ # self.write_fifo_queue = deque() # to microbit
+ # self.read_fifo_queue = deque() # from microbit
+ self.lock = threading.Lock()
+
+ def list(self, timeout=5) -> list:
+ microbit_ports = list_microbit() # return
+ if not microbit_ports:
+ self.node_instance.logger.error("未发现 micro:bit")
+ self.node_instance.pub_notification("未发现 micro:bit", type="ERROR")
+
+ return [str(i[0]) for i in microbit_ports]
+
+ # def connect(self, ip, timeout=5):
+ def connect(self, id, **kwargs):
+ self.port = id
+ # self.node_instance.logger.debug(f"args: {kwargs}")
+ if not kwargs.get("baudrate", None):
+ kwargs["baudrate"] = 115200
+ # if not kwargs.get("timeout", None):
+ timeout = kwargs["timeout"] # client输入
+ kwargs["timeout"] = 1 # 串口超时时间
+ ser = serial.Serial(self.port, **kwargs)
+ self.thing = ser
+ firmware_path = str(get_adapter_home_path() / "src" /
+ "usb_Microbit_firmware_4v1v2.hex")
+
+ t1 = time.time()
+ # time.sleep(0.1)
+ while self.node_instance._running:
+ if time.time() - t1 >= timeout / 2:
+ # 超时没收到version信息, 可能是第一次烧录
+ self.node_instance.logger.error("get version timeout")
+ ser.close()
+ self.node_instance.pub_notification("正在刷入固件...", type="INFO")
+ flash_makecode_file(firmware_path) # todo flash_hex_file
+ time.sleep(10)
+ return
+ # self.send_command(payload="__version__", msgid="query version")
+ self.send_command()
+ # ser.write(b"version\n") # 没写进去,microbit没有反应
+ data = self.uart_helper() # timeout, 为何read没东西? 第一次
+ self.node_instance.logger.debug(f"version query reply: {data}")
+ if data:
+ if data.get("version"):
+ self.node_instance.logger.debug(
+ f"usb microbit firmware -> {data['version']}")
+ version = data.get("version")
+ if version >= "0.4": # todo 0.5 set radio_03 其他不要动,固件变了
+ self.is_connected = True # 连接成功
+ self.thing = ser
+ self.node_instance.pub_notification("micro:bit 已连接", type="SUCCESS")
+ self.run_task_forever()
+ return "ok"
+ else:
+ self.thing.close()
+ self.node_instance.pub_notification("正在刷入固件...",
+ type="INFO")
+ flash_makecode_file(firmware_path)
+ time.sleep(10)
+ # self.node_instance.pub_notification(self.flash_finished_notification, type="SUCCESS")
+ return # 只有串口数据是 version_xx才退出循环
+
+ def status(self, **kwargs) -> bool:
+ pass
+
+ def disconnect(self):
+ self.is_connected = False
+ try:
+ self.thing.close()
+ except Exception:
+ pass
+ self.thing = None
+ self.node_instance.pub_notification(f'{self.node_instance.NODE_ID} 已断开', type="WARNING")
+
+ # 业务代码
+ def write(self, content):
+ if self.is_connected:
+ self.thing.write(content.encode('utf-8')) # todo 线程安全
+ return "ok"
+
+ def _send_microbit_messge(self, data):
+ message = self.node_instance.message_template()
+ msgid = data.get("msgid")
+ message["payload"]["message_id"] = msgid
+
+ output = data.get("output")
+ if output:
+ # 正常运行了请求的代码
+ message["payload"]["content"] = output
+ self.node_instance.publish(message)
+
+ # and sensor
+ message["payload"]["message_id"] = -1
+ message["payload"]["content"] = data["payload"]
+ self.node_instance.publish(message)
+ else:
+ message["payload"]["content"] = data["payload"]
+ self.node_instance.publish(message)
+
+ def uart_helper(self):
+ '''
+ 后台任务
+ 放入缓冲区
+ 1000个长度
+ 写入任务
+ 在同个循环里,避免跨线程
+ 使用queue跨线程
+
+ return
+ microbit output
+ '''
+ try:
+ data = self.thing.readline()
+ if data:
+ data = data.decode()
+ try:
+ data = eval(data) # todo, 来自 Microbit的数据 暂无安全问题
+ except (ValueError, SyntaxError) as e:
+ # raise e
+ self.node_instance.logger.error(f"{e}: {data}")
+ else:
+ return data
+ except UnicodeDecodeError as e:
+ self.node_instance.logger.error(e)
+ except serial.serialutil.SerialException:
+ self.node_instance.pub_notification("micro:bit 连接异常", type="WARNING")
+ time.sleep(3)
+ except Exception as e:
+ self.node_instance.logger.error(e)
+ self.node_instance.pub_notification(str(e), type="ERROR")
+ # self.node_instance.logger.exception("what!")
+ time.sleep(3)
+
+ @threaded
+ def run_task_forever(self):
+ while self.node_instance._running:
+ if self.is_connected:
+ # microbit node -> microbit adapter
+ # rate = 20
+ # time.sleep(1 / rate)
+ self.send_command()
+ response_from_microbit = self.uart_helper()
+ self.node_instance.logger.debug(f'read from microbit: {response_from_microbit}')
+ if not response_from_microbit:
+ continue
+ if response_from_microbit:
+ # if response_from_microbit.startswith('version_'):
+ '''
+ version = response_from_microbit.get("version")
+ if version and version >= "0.4":
+ # version信息, 状态机 todo
+ continue
+ '''
+ self._send_microbit_messge(response_from_microbit)
+ else:
+ break
+
+ def send_command(self, payload="0", msgid=-1):
+ '''
+ payload content
+ todo message id 对应
+ '''
+ self.lock.acquire()
+ rate = 10
+ time.sleep(1 / rate)
+ scratch3_message = {"msgid": msgid, "payload": payload}
+
+ scratch3_message = json.dumps(scratch3_message) + "\r\n"
+ scratch3_message_bytes = scratch3_message.encode('utf-8')
+ self.node_instance.logger.debug(f'send to microbit: {scratch3_message_bytes}')
+ self.thing.write(scratch3_message_bytes)
+ self.lock.release()
+
+
+class MicrobitExtension(Extension):
'''
use TokenBucket to limit message rate(pub) https://github.com/CodeLabClub/codelab_adapter_client_python/blob/master/codelab_adapter_client/utils.py#L25
'''
@@ -20,61 +216,68 @@ class UsbMicrobitProxy(Extension):
NODE_ID = "eim/extension_usb_microbit"
HELP_URL = "http://adapter.codelab.club/extension_guide/microbit/"
WEIGHT = 99
+ VERSION = "2.0.0"
DESCRIPTION = "使用 Microbit, 为物理世界编程"
- def __init__(self, bucket_token=20, bucket_fill_rate=10, **kwargs):
- super().__init__(bucket_token=bucket_token,
- bucket_fill_rate=bucket_fill_rate, **kwargs)
- self.microbitHelper = UsbMicrobitHelper(self)
- self.q = queue.Queue()
+ def __init__(self, **kwargs): # kwargs 接受启动参数
+ super().__init__(
+ bucket_token=100, # 默认是100条 hub模式消息量大
+ bucket_fill_rate=100,
+ **kwargs)
+ self.thing = ThingProxy(self)
+ # self.microbitHelper = MicrobitRadioHelper(self)
def run_python_code(self, code):
# fork from python extension
try:
- output = eval(code, {"__builtins__": None}, {
- "microbitHelper": self.microbitHelper,
- })
+ output = eval(
+ code,
+ {"__builtins__": None},
+ {
+ "thing": self.thing, # 直接调用方法
+ "connect": self.thing.connect,
+ "disconnect": self.thing.disconnect,
+ "list": self.thing.list,
+ })
except Exception as e:
output = str(e)
+ # 也作为提醒
+ self.pub_notification(output, type="ERROR")
return output
def extension_message_handle(self, topic, payload):
- '''
- test: codelab-message-pub -j '{"topic":"scratch/extensions/command","payload":{"node_id":"eim/extension_usb_microbit", "content":"display.show(\"c\")"}}'
- '''
self.logger.info(f'python code: {payload["content"]}')
- message_id = payload.get("message_id")
+ # message_id = payload.get("message_id")
python_code = payload["content"]
- if "microbitHelper" in python_code:
- output = self.run_python_code(python_code)
- payload["content"] = output
- message = {"payload": payload} # 无论是否有message_id都返回
+ # send_command 直接运行 结果呢? 异步
+ # message_id = payload.get("message_id")
+ output = self.run_python_code(python_code)
+ # 运行命令和其他不一致(异步)
+ try:
+ output = json.dumps(output)
+ except Exception:
+ output = str(output)
+ if "thing.send_command(" in python_code:
+ # payload
+ # todo 异步发送
+ payload["content"] = "ok"
+ message = {"payload": payload}
self.publish(message)
+
else:
- self.q.put(python_code)
+ payload["content"] = output
+ message = {"payload": payload}
+ self.publish(message)
def run(self):
- while self._running:
- # 写入才能读出
- # 检查是否打开
- if self.microbitHelper.ser:
- try:
- self.microbitHelper.send_command()
- # 一读一写 比较稳定, todo: CQRS , todo makecode create hex
- response_from_microbit = self.microbitHelper.get_response_from_microbit(
- )
- if response_from_microbit:
- message = self.message_template()
- message["payload"]["content"] = response_from_microbit[
- "payload"]
- self.publish(message)
- rate = 10
- time.sleep(1 / rate)
- except Exception as e:
- self.logger.debug(str(e))
- time.sleep(0.5)
- else:
- time.sleep(0.5)
+ time.sleep(0.1)
+
+ def terminate(self, **kwargs):
+ try:
+ self.thing.disconnect()
+ except Exception:
+ pass
+ super().terminate(**kwargs)
-export = UsbMicrobitProxy
+export = MicrobitExtension
diff --git a/docs/extensions_nodes_mirrors/extension_webUI_manager.py b/docs/extensions_nodes_mirrors/extension_webUI_manager.py
new file mode 100644
index 0000000..bc70f7a
--- /dev/null
+++ b/docs/extensions_nodes_mirrors/extension_webUI_manager.py
@@ -0,0 +1,31 @@
+import time
+
+from codelab_adapter.core_extension import Extension
+from codelab_adapter_client.topic import GUI_TOPIC
+from codelab_adapter_client.utils import open_path, is_win
+
+
+class WebUI_Manager(Extension):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.NODE_ID = "adapter/WebUI_Manager"
+
+ def extension_message_handle(self, topic, payload):
+ content = payload["content"]
+ if content == "openHostsDir":
+ # unix/windows
+ # unix /etc/hosts
+ # windows C:\Windows\System32\drivers\etc
+ if is_win():
+ hosts_path = 'C:\Windows\System32\drivers\etc'
+ else:
+ hosts_path = '/etc'
+ self.logger.debug(payload)
+ open_path(hosts_path)
+
+ def run(self):
+ while self._running:
+ time.sleep(0.5)
+
+
+export = WebUI_Manager
\ No newline at end of file
diff --git a/docs/extensions_nodes_mirrors/extension_webserver.py b/docs/extensions_nodes_mirrors/extension_webserver.py
index da7daee..aeb3566 100644
--- a/docs/extensions_nodes_mirrors/extension_webserver.py
+++ b/docs/extensions_nodes_mirrors/extension_webserver.py
@@ -9,8 +9,8 @@
from bottle import route, run, template, view
from codelab_adapter.core_extension import Extension
-from codelab_adapter.utils import threaded, get_local_ip
-from codelab_adapter_client.utils import get_adapter_home_path
+from codelab_adapter.utils import threaded
+from codelab_adapter_client.utils import get_adapter_home_path, get_local_ip
# html 文件所在目录
bottle.TEMPLATE_PATH = [
diff --git a/docs/extensions_nodes_mirrors/extension_wechat.py b/docs/extensions_nodes_mirrors/extension_wechat.py
index d5832a1..d880d37 100644
--- a/docs/extensions_nodes_mirrors/extension_wechat.py
+++ b/docs/extensions_nodes_mirrors/extension_wechat.py
@@ -3,14 +3,26 @@
自动登陆默认不启用,可通过配置项启用
kill itchat thread
'''
-import itchat, time
-from itchat.content import TEXT
+
import zmq
import pathlib
import threading
-from codelab_adapter.utils import threaded
+from codelab_adapter.utils import threaded, is_win
from codelab_adapter.core_extension import Extension
+if is_win():
+ import io
+ from contextlib import redirect_stdout, redirect_stderr
+ stdout = io.StringIO()
+ stderr = io.StringIO()
+ with redirect_stdout(stdout), redirect_stderr(stderr):
+ import itchat, time
+ from itchat.content import TEXT
+else:
+ import itchat, time
+ from itchat.content import TEXT
+
+
codelab_adapter_dir = pathlib.Path.home() / "codelab_adapter"
class WechatGateway(Extension):
diff --git a/docs/extensions_nodes_mirrors/extensions_nodes.json b/docs/extensions_nodes_mirrors/extensions_nodes.json
index 8f11470..497a103 100644
--- a/docs/extensions_nodes_mirrors/extensions_nodes.json
+++ b/docs/extensions_nodes_mirrors/extensions_nodes.json
@@ -1 +1 @@
-{"nodes": [{"value": "node_physical_blocks.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_physical_blocks.py"}, {"value": "node_vector.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_vector.py"}, {"value": "node_minecraft.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_minecraft.py"}, {"value": "node_jupyterlab.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_jupyterlab.py"}, {"value": "node_cozmo.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_cozmo.py"}, {"value": "node_eim_monitor.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_eim_monitor.py"}, {"value": "node_blender.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_blender.py"}, {"value": "node_motionSensor_gesture.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_motionSensor_gesture.py"}, {"value": "node_HCI.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_HCI.py"}, {"value": "node_motionSensor_proximity.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_motionSensor_proximity.py"}, {"value": "node_eim.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_eim.py"}, {"value": "node_raspberrypi.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_raspberrypi.py"}, {"value": "node_sonicPi.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_sonicPi.py"}, {"value": "node_Yanshee.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_Yanshee.py"}], "extensions": [{"value": "extension_simple_NLU.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_simple_NLU.py"}, {"value": "extension_microbit_radio.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_microbit_radio.py"}, {"value": "extension_eim.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_eim.py"}, {"value": "extension_Siri.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_Siri.py"}, {"value": "extension_http_eim.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_http_eim.py"}, {"value": "extension_webserver.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_webserver.py"}, {"value": "extension_calypso.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_calypso.py"}, {"value": "extension_jupyterlab.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_jupyterlab.py"}, {"value": "extension_eim_monitor.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_eim_monitor.py"}, {"value": "extension_mqtt_adapter.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_mqtt_adapter.py"}, {"value": "extension_pomodoros.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_pomodoros.py"}, {"value": "extension_tello.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_tello.py"}, {"value": "extension_mqtt_broker.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_mqtt_broker.py"}, {"value": "extension_kano_wand.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_kano_wand.py"}, {"value": "extension_NetworkZero.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_NetworkZero.py"}, {"value": "extension_Aqara_scene.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_Aqara_scene.py"}, {"value": "extension_RoboMaster.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_RoboMaster.py"}, {"value": "extension_eim_trigger.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_eim_trigger.py"}, {"value": "extension_python.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_python.py"}, {"value": "extension_arduino_uno.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_arduino_uno.py"}, {"value": "extension_stage.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_stage.py"}, {"value": "extension_uart_adapter.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_uart_adapter.py"}, {"value": "extension_python_exec.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_python_exec.py"}, {"value": "extension_wechat.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_wechat.py"}, {"value": "extension_usb_microbit.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_usb_microbit.py"}]}
\ No newline at end of file
+{"nodes": [{"value": "node_physical_blocks.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_physical_blocks.py"}, {"value": "node_vector.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_vector.py"}, {"value": "node_minecraft.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_minecraft.py"}, {"value": "node_yeelight.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_yeelight.py"}, {"value": "node_RoboMasterEP2.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_RoboMasterEP2.py"}, {"value": "node_jupyterlab.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_jupyterlab.py"}, {"value": "node_tello4.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_tello4.py"}, {"value": "node_cozmo.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_cozmo.py"}, {"value": "node_eim_monitor.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_eim_monitor.py"}, {"value": "node_blender.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_blender.py"}, {"value": "node_motionSensor_gesture.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_motionSensor_gesture.py"}, {"value": "node_HCI.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_HCI.py"}, {"value": "node_physical_blocks2.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_physical_blocks2.py"}, {"value": "node_thingDemo.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_thingDemo.py"}, {"value": "node_adapterMario.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_adapterMario.py"}, {"value": "node_motionSensor_proximity.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_motionSensor_proximity.py"}, {"value": "node_tello2.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_tello2.py"}, {"value": "node_webserver_flask.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_webserver_flask.py"}, {"value": "node_RoboMaster2.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_RoboMaster2.py"}, {"value": "node_overdrive2.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_overdrive2.py"}, {"value": "node_RoboMaster3.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_RoboMaster3.py"}, {"value": "node_overdrive.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_overdrive.py"}, {"value": "node_eim.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_eim.py"}, {"value": "node_tello3.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_tello3.py"}, {"value": "node_raspberrypi.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_raspberrypi.py"}, {"value": "node_sonicPi.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_sonicPi.py"}, {"value": "node_Yanshee.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_Yanshee.py"}, {"value": "node_alphamini.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_alphamini.py"}, {"value": "node_digimon.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/node_digimon.py"}], "extensions": [{"value": "extension_simple_NLU.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_simple_NLU.py"}, {"value": "extension_microbit_radio.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_microbit_radio.py"}, {"value": "extension_eim.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_eim.py"}, {"value": "extension_Siri.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_Siri.py"}, {"value": "extension_http_eim.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_http_eim.py"}, {"value": "extension_webserver.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_webserver.py"}, {"value": "extension_calypso.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_calypso.py"}, {"value": "extension_jupyterlab.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_jupyterlab.py"}, {"value": "extension_eim_monitor.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_eim_monitor.py"}, {"value": "extension_mqtt_adapter.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_mqtt_adapter.py"}, {"value": "extension_pomodoros.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_pomodoros.py"}, {"value": "extension_webUI_manager.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_webUI_manager.py"}, {"value": "extension_tello.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_tello.py"}, {"value": "extension_mqtt_broker.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_mqtt_broker.py"}, {"value": "extension_kano_wand.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_kano_wand.py"}, {"value": "extension_NetworkZero.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_NetworkZero.py"}, {"value": "extension_socket_server.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_socket_server.py"}, {"value": "extension_Aqara_scene.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_Aqara_scene.py"}, {"value": "extension_RoboMaster.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_RoboMaster.py"}, {"value": "extension_eim_trigger.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_eim_trigger.py"}, {"value": "extension_python.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_python.py"}, {"value": "extension_arduino_uno.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_arduino_uno.py"}, {"value": "extension_stage.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_stage.py"}, {"value": "extension_uart_adapter.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_uart_adapter.py"}, {"value": "extension_wechat.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_wechat.py"}, {"value": "extension_usb_microbit.py", "url": "https://adapter.codelab.club/extensions_nodes_mirrors/extension_usb_microbit.py"}]}
\ No newline at end of file
diff --git a/docs/extensions_nodes_mirrors/node_HCI.py b/docs/extensions_nodes_mirrors/node_HCI.py
index 686fdf4..0938b1d 100644
--- a/docs/extensions_nodes_mirrors/node_HCI.py
+++ b/docs/extensions_nodes_mirrors/node_HCI.py
@@ -1,44 +1,33 @@
'''
HCI: human–machine interaction
本插件支持将任何输入映射为鼠标键盘行为
-
-PyAutoGUI only runs on Windows, Mac, and Linux.
-If you lose control and need to stop the current PyAutoGUI function, keep moving the mouse cursor up and to the left.
-
-tips:
- currentMouseX, currentMouseY = pyautogui.position()
- pyautogui.moveTo(100, 150)
- pyautogui.click()
- pyautogui.moveRel(None, 10) # move mouse 10 pixels down
- pyautogui.typewrite('Hello world!', interval=0.25)
-
'''
import queue
import time
-import pyautogui # todo 自动安装
from codelab_adapter_client import AdapterNode
+from codelab_adapter_client.utils import install_requirement
+
+try:
+ import pyautogui
+except ModuleNotFoundError:
+ REQUIREMENTS = ["pyautogui"]
+ install_requirement(REQUIREMENTS)
+ import pyautogui
class HCINode(AdapterNode):
- '''
- Everything Is Message
- ref: https://github.com/CodeLabClub/codelab_adapter_extensions/blob/master/extensions_v2/extension_python_kernel.py
- '''
NODE_ID = "eim/node_HCI"
WEIGHT = 98
HELP_URL = "https://adapter.codelab.club/extension_guide/HCI/"
DESCRIPTION = "接管鼠标键盘"
VERSION = "1.1.0"
- def __init__(self):
- super().__init__()
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
self.q = queue.Queue()
def extension_message_handle(self, topic, payload):
- # self.q.put(payload)
- # payload = self.q.get()
- message_id = payload.get("message_id")
python_code = payload["content"]
try:
output = eval(python_code, {"__builtins__": None}, {
@@ -50,17 +39,14 @@ def extension_message_handle(self, topic, payload):
message = {"payload": payload}
self.publish(message)
- def exit_message_handle(self, topic, payload):
- self.terminate()
-
def run(self):
while self._running:
time.sleep(0.5)
-if __name__ == "__main__":
+def main(**kwargs):
try:
- node = HCINode()
+ node = HCINode(**kwargs)
node.receive_loop_as_thread()
node.run()
except KeyboardInterrupt:
@@ -69,4 +55,8 @@ def run(self):
node.logger.error(str(e))
node.pub_notification(str(e), type="ERROR")
time.sleep(0.05)
- node.terminate() # Clean up before exiting.
\ No newline at end of file
+ node.terminate() # Clean up before exiting.
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/docs/extensions_nodes_mirrors/node_RoboMaster2.py b/docs/extensions_nodes_mirrors/node_RoboMaster2.py
new file mode 100644
index 0000000..1be7dd4
--- /dev/null
+++ b/docs/extensions_nodes_mirrors/node_RoboMaster2.py
@@ -0,0 +1,136 @@
+'''
+pip install robomasterpy==0.1.1
+ node 在外层依赖即可,window mac会提示依赖
+# https://robomasterpy.nanmu.me/en/latest/
+# https://github.com/nanmu42/robo-playground
+'''
+
+import socket
+import time
+import json
+from loguru import logger
+
+from codelab_adapter_client import AdapterNode # todo 异步插件启动确认
+from codelab_adapter_client.thing import AdapterThing
+
+import robomasterpy as rm # rm.get_broadcast_ip(timeout=1)
+
+
+class EPProxy(AdapterThing):
+ '''
+ AdapterThing
+ init
+ self.thing_name
+ self.node_instance
+ self.is_connected = False
+ self.thing = None
+ python eval code
+ connect
+ robot.chassis_move(x=-1, z=30) # thing不存在
+ # 分派方法和参数
+ '''
+ def __init__(self, node_instance):
+ # todo pydanic
+ super().__init__(thing_name="Robomaster EP",
+ node_instance=node_instance)
+
+ def list(self, timeout=5):
+ try:
+ broadcast_ip = rm.get_broadcast_ip(timeout=timeout) # 多个怎么办?
+ return [str(broadcast_ip)]
+ except socket.timeout:
+ self.node_instance.pub_notification(
+ f"timeout({timeout}s) when get_broadcast_ip", type="ERROR")
+ return []
+ except Exception as e:
+ self.node_instance.pub_notification(
+ str(e), type="ERROR")
+ return []
+ # 销毁socket
+
+ def connect(self, ip, timeout=5):
+ # 修改 self.thing
+ self.thing = rm.Commander(ip, timeout)
+
+ def status(self, **kwargs) -> bool:
+ # check status
+ # query thing status, 与设备通信,检查 is_connected 状态,修改它
+ pass
+
+ def disconnect(self):
+ self.thing.close(
+ ) # 关闭实例,回收socket资源。注意这个命令并不会发送quit到机甲,避免打扰其他在线的Commander.
+ # 允许多个client控制一个机甲
+ self.is_connected = False
+ self.thing = None
+
+ # 转发
+ # cmd.chassis_move(x=-1, z=30)
+ # self.thing.chassis_move(x=-1, z=30)
+ # robot.thing.chassis_move(x=-1, z=30)
+
+
+class EPExtension(AdapterNode):
+ NODE_ID = "eim/node_RoboMaster2"
+ HELP_URL = "http://adapter.codelab.club/extension_guide/RoboMaster2/"
+ DESCRIPTION = "开火!RoboMaster"
+ VERSION = "2.0.0"
+
+ def __init__(self):
+ super().__init__(logger=logger)
+ self.ep = EPProxy(self) # create proxy object
+
+ def run_python_code(self, code):
+ try:
+ output = eval(
+ code,
+ {"__builtins__": None},
+ {
+ "robot": self.ep.thing, # 直接调用方法
+ "connect": self.ep.connect,
+ "disconnect": self.ep.disconnect,
+ "list": self.ep.list,
+ })
+ except Exception as e:
+ # logger.exception('what?')
+ output = e
+ return output
+
+ def extension_message_handle(self, topic, payload):
+ # todo 判断是否连接成功,否则报告连接问题
+
+ logger.info(f'code: {payload["content"]}')
+ python_code = payload["content"]
+ output = self.run_python_code(python_code)
+ try:
+ output = json.dumps(output)
+ except Exception:
+ output = str(output)
+ payload["content"] = output
+ message = {"payload": payload}
+ self.publish(message)
+
+ def run(self):
+ while self._running:
+ time.sleep(0.5)
+
+ # release robot proxy object
+ def terminate(self, **kwargs):
+ try:
+ if self.ep.is_connected:
+ self.ep.disconnect()
+ except Exception:
+ pass
+ super().terminate(**kwargs)
+
+
+if __name__ == "__main__":
+ try:
+ node = EPExtension()
+ node.receive_loop_as_thread()
+ node.run()
+ except Exception as e:
+ if node._running:
+ node.pub_notification(str(e), type="ERROR")
+ time.sleep(0.1)
+ node.terminate()
diff --git a/docs/extensions_nodes_mirrors/node_RoboMaster3.py b/docs/extensions_nodes_mirrors/node_RoboMaster3.py
new file mode 100644
index 0000000..3af5207
--- /dev/null
+++ b/docs/extensions_nodes_mirrors/node_RoboMaster3.py
@@ -0,0 +1,91 @@
+'''
+pip install robomaster codelab_adapter_client --upgrade
+doc: https://robomaster-dev.readthedocs.io/zh_CN/latest/python_sdk/beginner_ep.html
+仅支持
+ macOS >= 10.15
+ Window amd64
+
+策略作为外部 Node,能够和Adapter通信
+
+使用 EIM 测试: eim/extension_RoboMasterEP3
+'''
+
+import time
+from loguru import logger
+from codelab_adapter_client import AdapterNode
+from codelab_adapter_client.utils import get_or_create_node_logger_dir
+from robomaster import robot
+from robomaster import blaster
+
+node_logger_dir = get_or_create_node_logger_dir()
+debug_log = str(node_logger_dir / "debug.log")
+logger.add(debug_log, rotation="1 MB", level="DEBUG")
+
+
+class EP3Node(AdapterNode):
+ NODE_ID = "eim/extension_RoboMasterEP3"
+ HELP_URL = "https://adapter.codelab.club/extension_guide/RoboMaster_EP2/"
+ DESCRIPTION = "RoboMaster EP 3.0"
+
+ def __init__(self):
+ super().__init__(logger=logger)
+ self.ep_robot = None
+
+ def init_device(self, timeout=5, conn_type="sta"):
+ '''
+ conn_type
+ sta 机器人接入局域网
+ ap 机器人作为热点
+ '''
+ # todo timeout
+ self.ep_robot = robot.Robot()
+ self.ep_robot.initialize(conn_type=conn_type)
+ self.pub_notification("RoboMaster 已连接", type="SUCCESS")
+
+ def run_python_code(self, code):
+ try:
+ # 允许用户传入连接参数 init_device
+ output = eval(code, {"__builtins__": None}, {"ep_robot": self.ep_robot, "blaster": blaster, "init_device": self.init_device})
+ except Exception as e:
+ output = e
+ return output
+
+ def extension_message_handle(self, topic, payload):
+ self.logger.info(f'code: {payload["content"]}')
+ message_id = payload.get("message_id")
+ python_code = payload["content"]
+ if self.ep_robot:
+ output = self.run_python_code(python_code)
+ payload["content"] = str(output)
+ message = {"payload": payload}
+ self.publish(message)
+
+ def run(self):
+ "避免插件结束退出"
+ try:
+ self.init_device()
+ except Exception as e:
+ self.logger.error(e)
+ self.pub_notification(str(e), type="ERROR")
+ return
+ while self._running:
+ time.sleep(0.5)
+
+ def terminate(self, **kwargs):
+ try:
+ if self.ep_robot:
+ self.ep_robot.close()
+ self.ep_robot = None
+ except Exception as e:
+ self.pub_notification(str(e), type="ERROR")
+ time.sleep(0.1)
+ super().terminate(**kwargs)
+
+if __name__ == "__main__":
+ try:
+ node = EP3Node()
+ node.receive_loop_as_thread()
+ node.run()
+ except:
+ if node._running:
+ node.terminate()
\ No newline at end of file
diff --git a/docs/extensions_nodes_mirrors/node_RoboMasterEP2.py b/docs/extensions_nodes_mirrors/node_RoboMasterEP2.py
new file mode 100644
index 0000000..6257ecf
--- /dev/null
+++ b/docs/extensions_nodes_mirrors/node_RoboMasterEP2.py
@@ -0,0 +1,143 @@
+'''
+pip install robomasterpy==0.1.1
+ node 在外层依赖即可,window mac会提示依赖
+# https://robomasterpy.nanmu.me/en/latest/
+# https://github.com/nanmu42/robo-playground
+'''
+
+import socket
+import time
+import json
+from loguru import logger
+
+from codelab_adapter_client import AdapterNode # todo 异步插件启动确认
+from codelab_adapter_client.thing import AdapterThing
+
+import robomasterpy as rm # rm.get_broadcast_ip(timeout=1)
+
+
+class EPProxy(AdapterThing):
+ '''
+ AdapterThing
+ init
+ self.thing_name
+ self.node_instance
+ self.is_connected = False
+ self.thing = None
+ python eval code
+ connect
+ robot.chassis_move(x=-1, z=30) # thing不存在
+ # 分派方法和参数
+ '''
+ def __init__(self, node_instance):
+ # todo pydanic
+ super().__init__(thing_name="Robomaster EP",
+ node_instance=node_instance)
+
+ def list(self, timeout=5):
+ try:
+ broadcast_ip = rm.get_broadcast_ip(timeout=timeout) # 多个怎么办?
+ return [str(broadcast_ip)]
+ except socket.timeout:
+ # self.node_instance.pub_notification(f"获取设备IP超时({timeout}s)", type="ERROR")
+ self.node_instance.pub_notification("未发现 RoboMasterEP", type="ERROR")
+ return []
+ except Exception as e:
+ self.node_instance.pub_notification(
+ str(e), type="ERROR")
+ return []
+
+ def connect(self, ip, timeout=5):
+ # 修改 self.thing, connect失败呢?
+ try:
+ self.thing = rm.Commander(ip, timeout)
+ self.node_instance.pub_notification("RoboMasterEP 已连接", type="SUCCESS")
+ except Exception as e:
+ self.node_instance.pub_notification(str(e), type="ERROR")
+
+ def status(self, **kwargs) -> bool:
+ # check status
+ # query thing status, 与设备通信,检查 is_connected 状态,修改它
+ pass
+
+ def disconnect(self):
+ if self.thing:
+ self.thing.close() # 关闭实例,回收socket资源。注意这个命令并不会发送quit到机甲,避免打扰其他在线的Commander.
+ # 允许多个client控制一个机甲
+ self.is_connected = False
+ self.thing = None
+
+ # 转发
+ # cmd.chassis_move(x=-1, z=30)
+ # self.thing.chassis_move(x=-1, z=30)
+ # robot.thing.chassis_move(x=-1, z=30)
+
+
+class EPExtension(AdapterNode):
+ NODE_ID = "eim/node_RoboMasterEP2"
+ HELP_URL = "http://adapter.codelab.club/extension_guide/RoboMasterEP2/"
+ DESCRIPTION = "开火!RoboMaster EP"
+ VERSION = "2.0.1" # 处理connect问题
+
+ def __init__(self, **kwargs):
+ super().__init__(logger=logger, **kwargs)
+ self.ep = EPProxy(self) # create proxy object
+
+ def run_python_code(self, code):
+ try:
+ output = eval(
+ code,
+ {"__builtins__": None},
+ {
+ "robot": self.ep.thing, # 直接调用方法
+ "connect": self.ep.connect,
+ "disconnect": self.ep.disconnect,
+ "list": self.ep.list,
+ })
+ except Exception as e:
+ # logger.exception('what?')
+ output = e
+ return output
+
+ def extension_message_handle(self, topic, payload):
+ # todo 判断是否连接成功,否则报告连接问题
+
+ logger.info(f'code: {payload["content"]}')
+ python_code = payload["content"]
+ output = self.run_python_code(python_code)
+ try:
+ output = json.dumps(output)
+ except Exception:
+ output = str(output)
+ payload["content"] = output
+ message = {"payload": payload}
+ self.publish(message)
+
+ def run(self):
+ while self._running:
+ time.sleep(0.5)
+
+ # release robot proxy object
+ def terminate(self, **kwargs):
+ try:
+ if self.ep.is_connected:
+ self.ep.disconnect()
+ except Exception:
+ pass
+ super().terminate(**kwargs)
+
+
+def main(**kwargs):
+ try:
+ node = EPExtension(**kwargs)
+ node.receive_loop_as_thread()
+ node.run()
+ except Exception as e:
+ if node._running:
+ node.pub_notification(str(e), type="ERROR")
+ time.sleep(0.1)
+ node.terminate()
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/docs/extensions_nodes_mirrors/node_adapterMario.py b/docs/extensions_nodes_mirrors/node_adapterMario.py
new file mode 100644
index 0000000..ad8c80f
--- /dev/null
+++ b/docs/extensions_nodes_mirrors/node_adapterMario.py
@@ -0,0 +1,283 @@
+import asyncio
+import json
+import time
+try:
+ from bleak import BleakScanner, BleakClient, BleakError # 有些windows无法使用
+except Exception:
+ pass
+from codelab_adapter_client import AdapterNodeAio
+from codelab_adapter_client.thing import AdapterThing
+from codelab_adapter_client.utils import is_win
+
+from codelab_adapter_client.config import settings
+from loguru import logger
+debug_log = str(settings.NODE_LOG_PATH / "adapterMario.log")
+logger.add(debug_log, rotation="1 MB", level="DEBUG")
+
+
+# Class for the controller
+class MarioController(AdapterThing):
+ LEGO_CHARACTERISTIC_UUID = "00001624-1212-efde-1623-785feabcd123"
+ SUBSCRIBE_IMU_COMMAND = bytearray(
+ [0x0A, 0x00, 0x41, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01])
+ SUBSCRIBE_RGB_COMMAND = bytearray(
+ [0x0A, 0x00, 0x41, 0x01, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01])
+
+ def __init__(self, node_instance):
+ super().__init__(thing_name="Mario", node_instance=node_instance)
+ self.current_x = 0
+ self.current_y = 0
+ self.current_z = 0
+ self.is_connected = False
+ self.task = None
+
+ self.devices_list = {}
+ self.device_flag = "lego"
+ self.scanner = None
+ # self.q = queue.Queue()
+
+ def _detection_ble_callback(self, device, advertisement_data):
+ # devices_list 实时更新
+ if self.device_flag in str(device).lower():
+ self.devices_list[str(device.address)] = {
+ "name": str(device.address),
+ "peripheralId": str(device.address),
+ "rssi": device.rssi,
+ }
+ message = self.node_instance.message_template()
+ message["payload"]["message_type"] = "devices_list"
+ message["payload"]["content"] = list(self.devices_list.values())
+
+ asyncio.create_task(self.node_instance.publish(message))
+
+ async def list(self, timeout=5) -> list:
+ # logger.debug("list devices...")
+ self.devices_list = {} # 清空
+ try:
+ scanner = BleakScanner()
+ self.scanner = scanner
+ scanner.register_detection_callback(self._detection_ble_callback)
+ await scanner.start()
+ await asyncio.sleep(5.0)
+ await self.scanner.stop()
+ except BleakError as e:
+ # 提醒开启蓝牙
+ logger.error(e)
+ await self.node_instance.pub_notification(str(e), type="ERROR")
+ return []
+
+ async def _send_connect_reply(self):
+ # from RVR
+ payload = self.node_instance.connect_payload
+ message = {"payload": payload}
+ await self.node_instance.publish(message)
+
+ async def _send_disconnect_message(self):
+ await self.node_instance.pub_notification(
+ f'{self.node_instance.NODE_ID} 已断开', type="WARNING")
+
+ async def _connect(self, address):
+ async with BleakClient(address) as client:
+ await client.is_connected()
+ # 连接成功反馈
+ await self._send_connect_reply()
+ self.is_connected = True
+ await client.start_notify(self.LEGO_CHARACTERISTIC_UUID,
+ self.notification_handler)
+ await asyncio.sleep(0.1)
+ await client.write_gatt_char(self.LEGO_CHARACTERISTIC_UUID,
+ self.SUBSCRIBE_IMU_COMMAND)
+ await asyncio.sleep(0.1)
+ await client.write_gatt_char(self.LEGO_CHARACTERISTIC_UUID,
+ self.SUBSCRIBE_RGB_COMMAND)
+ while await client.is_connected():
+ await asyncio.sleep(0.1)
+ # 发送断开消息
+ # disconnect
+ await self._send_disconnect_message()
+
+ async def connect(self, address, timeout=5):
+ if not self.thing:
+ # self.thing = BleakClient(address)
+ # as task
+ # 等待future, 外部停掉
+ self.task = asyncio.create_task(
+ self._connect(address)) # todo 真正连上后发出
+ # await asyncio.sleep(3)
+ return True
+
+ async def status(self):
+ pass
+
+ async def disconnect(self):
+ if self.task:
+ self.task.cancel() # 让用户知道错误信息
+ self.is_connected = False
+ if self.thing:
+ await self.thing.disconnect()
+ self.thing = None
+ return True
+
+ ### 以下是具体设备驱动
+
+ def signed(self, char):
+ return char - 256 if char > 127 else char
+
+ async def _send_to_adapter(self, content, message_type="setProperty"):
+ message = self.node_instance.message_template()
+ message["payload"]["message_type"] = message_type
+ message["payload"]["content"] = content
+ # property_notify属性的变化
+ await self.node_instance.publish(message)
+
+ def property_notify(self, data):
+ '''
+ msg = {
+ "topic": "things/urn:lego:mario/state",
+ "messageType": "setProperty",
+ "data": data
+ }
+ '''
+ logger.debug(f'data: {data}')
+ asyncio.create_task(self._send_to_adapter(data))
+ # self.q.put(data) # sync -> async
+ # send to eim
+ # validate_msg = InputMsg(**msg)
+ # ee.emit("things/urn:lego:mario", validate_msg)
+
+ def notification_handler(self, sender, data):
+ # Camera sensor data
+ # todo eim
+ if data[0] == 8:
+ # RGB code
+ if data[5] == 0x0:
+ # print(data[4])
+ if data[4] == 0xb8:
+ logger.debug("Start tile")
+ self.property_notify({"barcode": "Start"})
+ if data[4] == 0xb7:
+ logger.debug("Goal tile")
+ self.property_notify({"barcode": "Goal"})
+ logger.debug("Barcode: " + " ".join(hex(n) for n in data))
+
+ # Red tile
+ elif data[6] == 0x15:
+ # print("Red tile")
+ self.property_notify({"color": "Red"})
+ # Green tile
+ elif data[6] == 0x25:
+ # print("Green tile")
+ self.property_notify({"color": "Green"})
+ elif data[6] == 0x17:
+ # print("Blue tile")
+ self.property_notify({"color": "Blue"})
+ elif data[6] == 0x17:
+ # print("Blue tile")
+ self.property_notify({"color": "Blue"})
+ elif data[6] == 0x13:
+ # print("White tile")
+ self.property_notify({"color": "White"})
+ elif data[6] == 0x18:
+ # # print("Yellow tile")
+ self.property_notify({"color": "Yellow"})
+ elif data[6] == 0x6A:
+ ## print("Brown tile")
+ self.property_notify({"color": "Brown"})
+ # No tile
+ elif data[6] == 0x1a:
+ # # print("No tile")
+ self.property_notify({"color": "NoColor"})
+
+ # Accelerometer data
+ elif data[0] == 7:
+ self.current_x = int((self.current_x * 0.5) +
+ (self.signed(data[4]) * 0.5))
+ self.current_y = int((self.current_y * 0.5) +
+ (self.signed(data[5]) * 0.5))
+ self.current_z = int((self.current_z * 0.5) +
+ (self.signed(data[6]) * 0.5))
+ self.property_notify({
+ "coordinate": {
+ "x": self.current_x,
+ "y": self.current_y,
+ "z": self.current_z
+ }
+ })
+ '''
+ # 在 scratch 中处理,利用hat积木的特性: 如无变化,只发生一次
+ if self.current_z > 15:
+ self.property_notify({"action": "backward"})
+ elif self.current_z < -15:
+ self.property_notify({"action": "forward"})
+ if self.current_x > 20:
+ self.property_notify({"action": "jump"}) # todo? 时间?
+ '''
+ # # print("X: %i | Y: %i | Z: %i" % (self.current_x, self.current_y, self.current_z))
+
+
+class MyNode(AdapterNodeAio):
+ NODE_ID = "eim/node_adapterMario"
+ HELP_URL = "https://adapter.codelab.club/extension_guide/adapterMario/"
+ DESCRIPTION = "登登登等登蹬"
+ VERSION = "2.1.0" # 设备掉线通知
+
+ def __init__(self, **kwargs):
+ super().__init__(logger=logger, bucket_token=300, bucket_fill_rate=300, **kwargs)
+ self.thing = MarioController(self)
+ self.connect_payload = None
+
+ async def run_python_code(self, code):
+ try:
+ output = await eval(
+ code,
+ {"__builtins__": None},
+ {
+ # "thing": self.thing.thing, # 推送风格
+ "connect": self.thing.connect,
+ "disconnect": self.thing.disconnect,
+ "list": self.thing.list,
+ })
+ except Exception as e:
+ output = e
+ return output
+
+ async def extension_message_handle(self, topic, payload):
+ self.logger.info(f'code: {payload["content"]}')
+ python_code = payload["content"]
+ if python_code.startswith('connect('):
+ # connect
+ self.connect_payload = payload
+ output = await self.run_python_code(python_code)
+ try:
+ output = json.dumps(output)
+ except Exception:
+ output = str(output)
+ if not python_code.startswith('connect('):
+ # connect 连上之后(异步)发送
+ payload["content"] = output
+ message = {"payload": payload}
+ await self.publish(message)
+
+ def run(self):
+ while self._running:
+ time.sleep(0.5)
+
+ async def terminate(self, **kwargs):
+ try:
+ await self.thing.disconnect()
+ except Exception:
+ pass
+ await super().terminate(**kwargs)
+
+
+def main(**kwargs):
+ try:
+ node = MyNode(**kwargs)
+ asyncio.run(node.receive_loop()) # CPU?
+ except Exception:
+ if node._running:
+ asyncio.run(node.terminate())
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/docs/extensions_nodes_mirrors/node_alphamini.py b/docs/extensions_nodes_mirrors/node_alphamini.py
new file mode 100644
index 0000000..2c4245d
--- /dev/null
+++ b/docs/extensions_nodes_mirrors/node_alphamini.py
@@ -0,0 +1,340 @@
+'''
+pip install alphamini==1.1.0
+https://web.ubtrobot.com/mini-python-sdk/guide.html
+https://github.com/marklogg/mini_demo.git
+
+内置行为: https://web.ubtrobot.com/mini-python-sdk/additional.html
+'''
+
+import asyncio
+from loguru import logger
+
+from codelab_adapter_client import AdapterNodeAio # todo 异步插件启动确认
+from codelab_adapter_client.thing import AdapterThing
+
+import mini.mini_sdk as MiniSdk
+from mini.apis.base_api import MiniApiResultType
+from mini.dns.dns_browser import WiFiDevice
+from mini.apis.api_sound import StartPlayTTS, StopPlayTTS, ControlTTSResponse
+from mini.apis.api_action import PlayAction, PlayActionResponse
+from mini.apis.api_action import GetActionList, GetActionListResponse, RobotActionType
+from mini.apis.api_setup import StartRunProgram
+from mini.apis.api_action import MoveRobot, MoveRobotDirection, MoveRobotResponse
+from mini.apis.api_expression import PlayExpression, PlayExpressionResponse
+from mini.apis.api_behavior import StartBehavior, StopBehavior
+
+from mini.apis.api_sence import FaceAnalysis, FaceAnalyzeResponse
+from mini.apis.api_sence import FaceDetect, FaceDetectResponse
+from mini.apis.api_sence import FaceRecognise, FaceRecogniseResponse
+# 使用异步节点
+
+# 红外测距
+from mini.apis.api_sence import GetInfraredDistance, GetInfraredDistanceResponse
+# 物体识别 水果 鲜花 手势
+from mini.apis.api_sence import ObjectRecognise, RecogniseObjectResponse, ObjectRecogniseType
+
+
+class RobotProxy(AdapterThing):
+ '''
+ AdapterThing
+ init
+ self.thing_name
+ self.node_instance
+ self.is_connected = False
+ self.thing = None
+ '''
+ def __init__(self, node_instance):
+ # todo pydanic
+ super().__init__(thing_name="悟空机器人(alphamini)", node_instance=node_instance)
+
+ async def list(self):
+ # 需要在UI中填入序列号连接
+ # 课堂 在UI输入序列号连接
+ # self._ensure_connect()
+ return
+
+ '''
+ results = await MiniSdk.get_device_list(10) # 无法搜到
+ # print(results)
+ return [str(i) for i in results]
+ '''
+
+ async def connect(self, robot_name):
+ # 修改 self.thing
+ if self.is_connected:
+ return f"{self.thing_name} already connected"
+ else:
+ # robot_name = kwargs.get("robot_name") # todo
+ self.thing: WiFiDevice = await MiniSdk.get_device_by_name(
+ robot_name, 10)
+ if self.thing:
+ is_success = await MiniSdk.connect(self.thing)
+ if is_success:
+ self.is_connected = True
+ # (resultType, response) = await StartRunProgram().execute() #
+ await MiniSdk.enter_program()
+ # await self.say()
+ # await asyncio.sleep(100)
+ return is_success
+
+ def status(self, **kwargs) -> bool:
+ # check status
+ # query thing status, 与设备通信,检查 is_connected 状态,修改它
+ pass
+
+ # disconnect, 兼容
+ async def quit(self):
+ self._ensure_connect()
+ await MiniSdk.quit_program()
+ await MiniSdk.release()
+ self.is_connected = False
+ self.thing = None
+
+ async def disconnect(self):
+ return await self.quit()
+
+ ################## 以下是业务逻辑
+
+ async def GetActionList(self):
+ self._ensure_connect()
+ # robot.play(name="bow",speed="slow",operation="start")
+ block: GetActionList = GetActionList(action_type=RobotActionType.INNER)
+ # response:GetActionListResponse
+ (resultType, response) = await block.execute()
+ return response
+
+ async def play_action(self, **kwargs):
+ '''
+ https://web.ubtrobot.com/mini-python-sdk/additional.html#id2
+ https://web.ubtrobot.com/mini-python-sdk/additional.html#id3
+
+ 010 打招呼
+ 011 点头
+ 012 俯卧撑
+ 013 武术
+ 014 太极
+ 027 坐下
+ 031 蹲下
+ 017 举双手
+ 015 欢迎
+ 037 摇头
+ Surveillance_001 打招呼
+ Surveillance_004 飞吻
+ Surveillance_006 卖萌
+ action_016 再见
+ action_014 邀请
+ action_005 点赞
+ '''
+ self._ensure_connect()
+ block: PlayAction = PlayAction(**kwargs)
+ # response: PlayActionResponse
+ (resultType, response) = await block.execute()
+ return response
+
+ async def play_expression(self, **kwargs):
+ '''
+ codemao9 打喷嚏
+ codemao13 疑问
+ codemao16 贱贱的笑
+ codemao20 眨眼
+ emo_020 发呆
+ codemao19 爱心
+ '''
+ self._ensure_connect()
+ # https://web.ubtrobot.com/mini-python-sdk/additional.html#id4
+ block: PlayExpression = PlayExpression(**kwargs)
+ # response: PlayExpressionResponse
+ (resultType, response) = await block.execute()
+ return response
+
+ async def play_behavior(self, **kwargs):
+ '''
+ custom_0035 生日快乐
+ dance_0008 虫儿飞
+ '''
+ self._ensure_connect()
+ # https://web.ubtrobot.com/mini-python-sdk/additional.html#id4
+ block = StartBehavior(**kwargs)
+ if kwargs.get("is_serial"):
+ (resultType, response) = await block.execute()
+ return response
+ else:
+ return await block.execute()
+
+ async def stop_behavior(self, **kwargs):
+ '''
+ custom_0035 生日快乐
+ dance_0008 虫儿飞
+ '''
+ self._ensure_connect()
+ # https://web.ubtrobot.com/mini-python-sdk/additional.html#id4
+ block = StopBehavior(**kwargs)
+ (resultType, response) = await block.execute()
+ return response
+
+ async def move(self, step=1, direction="FORWARD"):
+ '''
+ FORWARD : 向前
+ BACKWARD : 向后
+ LEFTWARD : 向左
+ RIGHTWARD : 向右
+ '''
+ self._ensure_connect()
+ block: MoveRobot = MoveRobot(step=step,
+ direction=MoveRobotDirection[direction])
+ (resultType, response) = await block.execute()
+ return response
+
+ async def bow(self):
+ self._ensure_connect()
+ # robot.play(name="bow",speed="slow",operation="start")
+ pass
+
+ async def say(self, **kwargs):
+ self._ensure_connect()
+ block: StartPlayTTS = StartPlayTTS(**kwargs)
+ # 返回元组, response是个ControlTTSResponse
+ (resultType, response) = await block.execute()
+
+ ### 人脸api: https://github.com/marklogg/mini_demo/blob/42cacfa5c8e8a47ed343ab61c691c1973b17e742/test/test_sence.py#L18
+ async def face_detect(self, timeout=10):
+ block: FaceDetect = FaceDetect(timeout=timeout)
+ # response: FaceDetectResponse
+ (resultType, response) = await block.execute()
+ logger.debug(f'test_face_detect result: {response}')
+
+ assert resultType == MiniApiResultType.Success, 'face_detect timetout'
+ assert response is not None and isinstance(response, FaceDetectResponse), 'face_detect result unavailable'
+ assert response.isSuccess, 'face_detect failed'
+
+ return response.count # 人脸个数
+
+ async def face_analysis(self, timeout=10):
+ # https://github.com/marklogg/mini_demo/blob/42cacfa5c8e8a47ed343ab61c691c1973b17e742/test/test_sence.py#L43
+ block: FaceAnalysis = FaceAnalysis(timeout=timeout)
+ # response: FaceAnalyzeResponse
+ (resultType, response) = await block.execute()
+
+ logger.debug(f'test_face_analysis result: {response}')
+ # print('code = {0}, error={1}'.format(response.resultCode, errors.get_vision_error_str(response.resultCode)))
+
+ assert resultType == MiniApiResultType.Success, 'face_analysis timetout'
+ assert response is not None and isinstance(response, FaceAnalyzeResponse), 'face_analysis result unavailable'
+ assert response.isSuccess, 'face_analysis failed'
+ # gender: 小于50为女性,大于50为男性
+ return response # {"age": 24, "gender": 99, "height": 238, "width": 238}
+
+ async def face_recognise(self, timeout=10):
+ # https://github.com/marklogg/mini_demo/blob/42cacfa5c8e8a47ed343ab61c691c1973b17e742/test/test_sence.py#L182
+ # response : FaceRecogniseResponse
+ (resultType, response) = await FaceRecognise(timeout=timeout).execute()
+
+ logger.debug(f'face_recognise result: {response}')
+
+ assert resultType == MiniApiResultType.Success, 'face_recognise timetout'
+ assert response is not None and isinstance(response,
+ FaceRecogniseResponse), 'face_recognise result unavailable'
+ assert response.isSuccess, 'face_recognise failed'
+ return response.faceInfos # id name
+
+ # 测试获取红外探测距离
+ async def get_infrared_distance(self):
+ (resultType, response) = await GetInfraredDistance().execute()
+
+ # print(f'test_get_infrared_distance result: {response}')
+
+ assert resultType == MiniApiResultType.Success, 'get_infrared_distance timetout'
+ assert response is not None and isinstance(response,
+ GetInfraredDistanceResponse), 'get_infrared_distance result unavailable'
+ assert response.distance > 0, 'get_infrared_distance failed'
+ return response.distance
+
+ # 物体识别
+ async def recognise_gesture(self, timeout=10):
+ """测试物体(手势)识别
+ """
+ # object_type: 支持FLOWER, FRUIT, GESTURE 三类物体
+ block: ObjectRecognise = ObjectRecognise(object_type=ObjectRecogniseType.GESTURE, timeout=timeout)
+ # response : RecogniseObjectResponse
+ (resultType, response) = await block.execute()
+
+ logger.debug(f'recognise_gesture result: {response}')
+
+ assert resultType == MiniApiResultType.Success, 'recognise_gesture timetout'
+ assert response is not None and isinstance(response,
+ RecogniseObjectResponse), 'recognise_gesture result unavailable'
+ assert response.isSuccess, 'recognise_gesture failed'
+ return response.objects
+
+ async def recognise_fruit(self, timeout=10):
+ """测试物体(水果)识别
+ """
+ # object_type: 支持FLOWER, FRUIT, GESTURE 三类物体
+ block: ObjectRecognise = ObjectRecognise(object_type=ObjectRecogniseType.FRUIT, timeout=timeout)
+ # response : RecogniseObjectResponse
+ (resultType, response) = await block.execute()
+
+ logger.debug(f'test_object_recognise_fruit result: {response}')
+
+ assert resultType == MiniApiResultType.Success, 'recognise_fruit timetout'
+ assert response is not None and isinstance(response,
+ RecogniseObjectResponse), 'recognise_fruit result unavailable'
+ assert response.isSuccess, 'recognise_fruit failed'
+ return response.objects
+
+ # 语音识别
+ # https://github.com/marklogg/mini_demo/blob/42cacfa5c8e8a47ed343ab61c691c1973b17e742/test/test_event.py#L16
+
+class MiniExtension(AdapterNodeAio):
+ NODE_ID = "eim/node_alphamini"
+ HELP_URL = "https://adapter.codelab.club/extension_guide/node_alphamini/"
+ DESCRIPTION = "悟空是一只伪装成机器人的猴子"
+ VERSION = "1.2.0"
+
+ def __init__(self, **kwargs):
+ super().__init__(logger=logger, **kwargs)
+ self.thing = RobotProxy(self) # create robot proxy object
+
+ async def run_python_code(self, code):
+ try:
+ output = await eval(
+ code,
+ {"__builtins__": None},
+ {
+ # "api_instance": self.thing.api_instance,
+ "robot": self.thing
+ })
+ except Exception as e:
+ # todo 完整错误
+ # logger.exception('what?')
+ output = e
+ return output
+
+ async def extension_message_handle(self, topic, payload):
+ # todo 判断是否连接成功,否则报告连接问题
+
+ logger.info(f'code: {payload["content"]}')
+ # message_id = payload.get("message_id")
+ python_code = payload["content"]
+ output = await self.run_python_code(python_code)
+ payload["content"] = str(output)
+ message = {"payload": payload}
+ await self.publish(message)
+
+ # release robot proxy object
+ async def terminate(self, **kwargs):
+ if self.thing.is_connected:
+ await self.thing.disconnect()
+ await super().terminate(**kwargs)
+
+
+def main(**kwargs):
+ try:
+ node = MiniExtension(**kwargs)
+ asyncio.run(node.receive_loop())
+ except KeyboardInterrupt:
+ if node._running:
+ asyncio.run(node.terminate())
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/docs/extensions_nodes_mirrors/node_blender.py b/docs/extensions_nodes_mirrors/node_blender.py
index acca811..63aa3a9 100644
--- a/docs/extensions_nodes_mirrors/node_blender.py
+++ b/docs/extensions_nodes_mirrors/node_blender.py
@@ -33,9 +33,6 @@ def extension_message_handle(self, topic, payload):
message = {"payload": payload}
self.publish(message)
- def exit_message_handle(self, topic, payload):
- self.terminate()
-
@threaded
def run(self):
while self._running:
diff --git a/docs/extensions_nodes_mirrors/node_cozmo.py b/docs/extensions_nodes_mirrors/node_cozmo.py
index 0c9b487..516fe43 100644
--- a/docs/extensions_nodes_mirrors/node_cozmo.py
+++ b/docs/extensions_nodes_mirrors/node_cozmo.py
@@ -2,68 +2,114 @@
usage:
python cozmo_server.py
+多台机器人
+ https://github.com/anki/cozmo-python-sdk/blob/dd29edef18748fcd816550469195323842a7872e/examples/multi_robot/multi_robot_unified.py
+ 默认采用优先发现原则 https://github.com/anki/cozmo-python-sdk/blob/dd29edef18748fcd816550469195323842a7872e/src/cozmo/run.py#L400
ref:
https://github.com/anki/cozmo-python-sdk/blob/master/examples/apps/remote_control_cozmo.py#L335
+
+get status
+ https://github.com/wwj718/calypso/blob/master/server.py#L353
+
+调试
+ https://github.com/anki/cozmo-python-sdk/blob/master/examples/apps/cli.py
'''
import queue
+import json
import time
-import importlib
-from loguru import logger
from codelab_adapter_client import AdapterNode
-from codelab_adapter_client.utils import get_or_create_node_logger_dir, install_requirement
-# import os
-# logger.warning(dict(os.environ))
-# log for debug
-node_logger_dir = get_or_create_node_logger_dir()
-debug_log = str(node_logger_dir / "debug.log")
+from codelab_adapter_client.utils import install_requirement
+from codelab_adapter_client.thing import AdapterThing
+from codelab_adapter_client.utils import threaded
+
+import cozmo
+from cozmo.util import degrees, distance_mm, speed_mmps, Pose
+from codelab_adapter_client.config import settings
+from loguru import logger
+debug_log = str(settings.NODE_LOG_PATH / "cozmo.log")
logger.add(debug_log, rotation="1 MB", level="DEBUG")
-class CozmoNode(AdapterNode):
- NODE_ID = "eim/node_cozmo"
- WEIGHT = 100
- HELP_URL = "https://adapter.codelab.club/extension_guide/cozmo/"
- VERSION = "1.2.1"
- DESCRIPTION = "最好的 AI 教育机器人之一"
- REQUIREMENTS = ["cozmo"]
+class CozmoProxy(AdapterThing):
+ def __init__(self, node_instance):
+ super().__init__(thing_name="Cozmo", node_instance=node_instance)
+ self.disconnect_flag = False
- def __init__(self):
- super().__init__(logger=logger)
- self.q = queue.Queue()
+ def _say_hi(self, robot):
+ # pass
+ # robot.say_text("hi").wait_for_completed()
+ pass
- def _import_requirement_or_import(self):
- requirement = self.REQUIREMENTS
+ def list(self, timeout=5) -> list:
+ if self.thing:
+ # 检查是否连接正常
+ return ["Cozmo"]
try:
- importlib.import_module("cozmo")
- except ModuleNotFoundError:
- self.pub_notification(f'try to install {" ".join(requirement)}...')
- install_requirement(requirement)
- self.pub_notification(f'{" ".join(requirement)} installed!')
- importlib.import_module("cozmo")
- import cozmo
- from cozmo.util import degrees, distance_mm, speed_mmps
- global cozmo, degrees, distance_mm, speed_mmps # make it global
+ # message id返回
+ cozmo.run_program(self._say_hi) # 联通性测试!
+ return ["Cozmo"]
+ except: # 不能加Exception 协程
+ # cozmo 有问题? 如果正常连接呢?
+ # self.node_instance.logger.error()
+ self.node_instance.pub_notification("未发现 Cozmo",
+ type="ERROR")
+ # self.node_instance.terminate()
+ return []
- def extension_message_handle(self, topic, payload):
- self.q.put(payload)
+ @threaded
+ def _connect(self):
+ # RuntimeError: This event loop is already running
+ cozmo.run_program(self.cozmo_program)
+ # todo 需要在主线程打开
+ # cozmo.run_program(self.cozmo_program, use_3d_viewer=True, use_viewer=True)
+ '''
+ try:
+ # 阻塞
+ except:
+ self.thing = None # ??
+ '''
+
+ def connect(self, ip, timeout=5):
+ if self.thing:
+ # 之前连过
+ return
+ self.is_connected = True
+ self._connect()
+ time.sleep(0.5)
- def exit_message_handle(self, topic, payload):
- self.terminate()
+ def status(self, **kwargs) -> bool:
+ pass
+ def disconnect(self):
+ # self.node_instance
+ self.node_instance.pub_notification(f'{self.node_instance.NODE_ID} 已断开', type="WARNING")
+ if self.thing:
+ try:
+ self.thing.say_text("see you later").wait_for_completed()
+ except Exception as e:
+ self.node_instance.logger.error(str(e))
+ self.thing = None
+ self.is_connected = False
+ self.disconnect_flag = True
+ time.sleep(0.1) # 等待检测
+ self.disconnect_flag = False
+ # self.node_instance.terminate() # 断开 重复连接cozmo可能有问题
+
+ # 业务
def pub_event(self, event_name, event_param=""):
- message = self.message_template()
+ message = self.node_instance.message_template()
message["payload"]["message_type"] = "device_event"
message["payload"]["content"] = {
"event_name": event_name,
"event_param": event_param
}
- self.publish(message)
+ self.node_instance.publish(message)
def onObjectTapped(self, evt, obj, **kwargs):
# 事件数量巨大
object_id = obj.object_id
- self.logger.debug(object_id)
+ self.node_instance.logger.debug(object_id)
event_name = "ObjectTapped"
event_param = object_id
self.pub_event(event_name, event_param)
@@ -95,18 +141,18 @@ def onObjectMovingStarted(self, evt, obj, **kwargs):
# face
def onFaceAppeared(self, evt, face, **kwargs):
event_name = "FaceAppeared"
- self.logger.debug(face.name)
- self.logger.debug(face.known_expression)
+ self.node_instance.logger.debug(face.name)
+ self.node_instance.logger.debug(face.known_expression)
event_param = face.known_expression
self.pub_event(event_name, event_param)
def onFaceObserved(self, evt, face, **kwargs):
# expression -> FACIAL_EXPRESSION_HAPPY("happy")
event_name = "FaceObserved"
- self.logger.debug(face.name)
- self.logger.debug(face.known_expression)
+ self.node_instance.logger.debug(face.name)
+ self.node_instance.logger.debug(face.known_expression)
event_param = face.known_expression
- self.logger.debug(
+ self.node_instance.logger.debug(
f'face name -> {face.name}, face expression -> {face.known_expression}'
)
if event_param:
@@ -120,29 +166,29 @@ def onFaceDisappeared(self, evt, face, **kwargs):
# pets
def onPetAppeared(self, evt, pet, **kwargs):
# 与face机制不一致
- self.logger.debug("PetAppeared")
- self.logger.debug(pet.pet_type)
+ self.node_instance.logger.debug("PetAppeared")
+ self.node_instance.logger.debug(pet.pet_type)
event_name = "PetAppeared"
event_param = pet.pet_type
self.pub_event(event_name, event_param)
def onPetObserved(self, evt, pet, **kwargs):
- self.logger.debug("PetObserved")
- self.logger.debug(pet.pet_type)
+ self.node_instance.logger.debug("PetObserved")
+ self.node_instance.logger.debug(pet.pet_type)
event_name = "PetObserved"
event_param = pet.pet_type
self.pub_event(event_name, event_param)
def onPetDisappeared(self, evt, pet, **kwargs):
- self.logger.debug("PetDisappeared")
- self.logger.debug(pet.pet_type)
+ self.node_instance.logger.debug("PetDisappeared")
+ self.node_instance.logger.debug(pet.pet_type)
event_name = "PetDisappeared"
event_param = pet.pet_type
self.pub_event(event_name, event_param)
def onRobotObservedMotion(self, evt, **kwargs):
event_name = "RobotObservedMotion" # EvtRobotObservedMotion
- logger.debug(event_name)
+ self.node_instance.logger.debug(event_name)
self.pub_event(event_name)
def cozmo_program(self, robot):
@@ -153,6 +199,10 @@ def cozmo_program(self, robot):
cozmo.world.EvtNewCameraImage
'''
# Object
+ self.thing = robot
+ self.node_instance.pub_notification("Cozmo 已连接", type="SUCCESS")
+ robot.say_text("hi").wait_for_completed()
+ # robot.enable_facial_expression_estimation(enable=True)
robot.add_event_handler(cozmo.objects.EvtObjectTapped,
self.onObjectTapped)
# ObjectObserved 发送频率非常高 会导致冲刷缓存变量(todo 优化 js extension 消息接收机制)
@@ -165,7 +215,13 @@ def cozmo_program(self, robot):
robot.add_event_handler(cozmo.objects.EvtObjectMovingStarted,
self.onObjectMovingStarted)
- # face
+ # face todo 启动人脸识别 enable
+ '''
+ 表情 启动
+ http://cozmosdk.anki.com/docs/generated/cozmo.robot.html#cozmo.robot.Robot.enable_facial_expression_estimation
+ robot.enable_facial_expression_estimation(enable=True)
+ http://cozmosdk.anki.com/docs/generated/cozmo.faces.html#cozmo.faces.FACIAL_EXPRESSION_UNKNOWN
+ '''
robot.add_event_handler(cozmo.faces.EvtFaceAppeared,
self.onFaceAppeared)
robot.add_event_handler(cozmo.faces.EvtFaceObserved,
@@ -182,35 +238,97 @@ def cozmo_program(self, robot):
# world Camera
# robot.add_event_handler(cozmo.camera.EvtRobotObservedMotion, self.onRobotObservedMotion)
- self.pub_notification("Cozmo Connected!", type="SUCCESS")
- while self._running:
- time.sleep(0.05)
- if not self.q.empty():
- payload = self.q.get()
- self.logger.info(f'python: {payload}')
- message_id = payload.get("message_id")
+ while self.node_instance._running:
+ if self.disconnect_flag:
+ # self.thing = None
+ # self.is_connected = False
+ break
+ time.sleep(0.1)
+ # 消息取来在内部运行
+ '''
+ if not self.node_instance.q.empty():
+ payload = self.node_instance.q.get()
+ # self.node_instance.logger.info(f'python: {payload}')
python_code = payload["content"]
-
try:
output = eval(python_code, {"__builtins__": None}, {
+ "robot": robot,
"cozmo": cozmo,
- "robot": robot
})
except Exception as e:
output = e
- self.pub_notification(str(e), type="ERROR")
+ self.node_instance.pub_notification(str(e), type="ERROR")
payload["content"] = str(output)
message = {"payload": payload}
self.publish(message)
+ # 把 message id 丢出去
+ '''
+
+
+class CozmoNode(AdapterNode):
+ NODE_ID = "eim/node_cozmo"
+ WEIGHT = 100
+ HELP_URL = "https://adapter.codelab.club/extension_guide/cozmo/"
+ VERSION = "2.1.0" # 支持表情
+ DESCRIPTION = "最好的 AI 教育机器人之一"
+
+ def __init__(self, **kwargs):
+ super().__init__(logger=logger, **kwargs)
+ # self.q = queue.Queue()
+ self.thing = CozmoProxy(self)
+
+ def extension_message_handle(self, topic, payload):
+ python_code = payload["content"]
+ # if python_code.startswith("robot."):
+ # self.q.put(payload)
+ try:
+ output = eval(
+ python_code,
+ {"__builtins__": None},
+ {
+ # "robot": robot,
+ "list": self.thing.list,
+ "connect": self.thing.connect,
+ "disconnect": self.thing.disconnect,
+ "robot": self.thing.thing,
+ "cozmo": cozmo,
+ "degrees": degrees,
+ "distance_mm": distance_mm,
+ "speed_mmps": speed_mmps,
+ "Pose": Pose,
+ "dir": dir,
+ "help": help
+ })
+ except Exception as e:
+ output = e
+ self.pub_notification(str(e), type="ERROR")
+ try:
+ output = json.dumps(output) # 单引号 json
+ except Exception:
+ output = str(output)
+ payload["content"] = str(output)
+ message = {"payload": payload}
+ self.publish(message)
def run(self):
- self._import_requirement_or_import()
- cozmo.run_program(self.cozmo_program)
+ while self._running:
+ # "robot": self.thing.thing,
+ if self.thing.thing and not self.thing.thing.world.conn.is_connected:
+ self.logger.debug(f"conn: {self.thing.thing.world.conn.is_connected}")
+ # self.thing.disconnect()
+ self.terminate()
+ self.logger.debug("go on...")
+ time.sleep(0.5)
+ # 发现,已经连接了,如果无法发现则说明有问题
+ # 利用 except 知道不存在 list
+ def terminate(self, **kwargs):
+ super().terminate(**kwargs)
-if __name__ == "__main__":
+
+def main(**kwargs):
try:
- node = CozmoNode()
+ node = CozmoNode(**kwargs)
node.receive_loop_as_thread()
node.run()
except KeyboardInterrupt:
@@ -220,3 +338,7 @@ def run(self):
node.pub_notification(str(e), type="ERROR")
time.sleep(0.05)
node.terminate() # Clean up before exiting.
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/docs/extensions_nodes_mirrors/node_digimon.py b/docs/extensions_nodes_mirrors/node_digimon.py
new file mode 100644
index 0000000..ba0cc9f
--- /dev/null
+++ b/docs/extensions_nodes_mirrors/node_digimon.py
@@ -0,0 +1,120 @@
+'''
+fork from node_webserver_flask
+'''
+import time
+import os
+import sys
+import webbrowser
+from loguru import logger
+from codelab_adapter_client import AdapterNode
+from codelab_adapter_client.utils import get_or_create_node_logger_dir, install_requirement, threaded, get_local_ip, send_simple_message
+from flask import Flask
+from flask import request
+
+# log for debug
+node_logger_dir = get_or_create_node_logger_dir()
+debug_log = str(node_logger_dir / "debug.log")
+logger.add(debug_log, rotation="1 MB", level="DEBUG")
+
+app = Flask(__name__)
+
+
+@app.route('/')
+def hello_world():
+ return 'Hello, 被选召的孩子!'
+
+
+@app.route('/digimon')
+def digimon():
+ # queue to adapter
+ name = request.args.get('name', default=None)
+ # send
+ send_simple_message(f'digimon {name}')
+ # logger.debug
+ digimon_info = {
+ "microbit": {
+ "name": "micro:bit",
+ "img": "https://img/",
+ },
+ "cozmo": {
+ "name": "Cozmo",
+ "description": "拥有惊人的灵活性,像微缩版的瓦力,性格阴晴不定,做游戏时喜欢耍小聪明",
+ "img": "https://scratch3-files.just4fun.site/cozmo-gif.gif",
+ "url": "https://adapter.codelab.club/extension_guide/cozmo/"
+ },
+ "alphamini": {},
+ "codelab": {},
+ "mushrooms":{},
+ }
+
+ return f'''
+
+
++++ {digimon_info.get(name).get('description')} +
+
+
## 启动 CodeLab Adapter 后,与 scratch3 无法通信怎么办?
-检查下是不是打开了科学上网的软件。
+检查下是不是打开了科学上网的软件, 不要使用全局模式。
## CodeLab Adapter 可以支持其他平台吗?
@@ -62,7 +62,7 @@ CodeLab Adapter 可以支持其他编程平台吗?而不只是在 CodeLab 的
CodeLab Adapter 几乎支持任何平台,无论是 Scratch 3.0 构建的还是 blockly 构建的(如 Tynker 和 code.org)的,或者你用其他什么黑魔法构建的,都没问题!
-这是目前的接入文档:[codelab-adapter 支持第三方平台](https://blog.just4fun.site/post/少儿编程/codelab-adapter-open-plan/)。
+这是目前的接入文档:[codelab-adapter 支持第三方平台](https://wwj718.github.io/post/少儿编程/codelab-adapter-open-plan/)。
相关的合作条款我们正在构建中。
@@ -70,24 +70,33 @@ CodeLab Adapter 几乎支持任何平台,无论是 Scratch 3.0 构建的还是
来信请注明公司/组织的一些基本信息,以及你们正在做的事情 :)
-## Python 版本(CodeLab Adapter 3.0.0)
+## Python 版本(CodeLab Adapter 4.9.5)
我们在不同操作系统打包时,使用的 Python 版本不同。
-- Windows:3.7.4
-- macOS:3.7.2
-- Raspbian:3.7.3
-- Ubuntu:3.7.5
+- Windows:3.7.6
+- macOS:3.8.7
+- Raspbian:3.7
+- Ubuntu:3.7
+
+详情可以查看WebUI菜单里的 `环境 > 查看`
## 如何使用 Python 拓展 Scratch 的能力?
-- [json message](/dev_guide/json-message/)。
-- 参考[使用 Python 拓展 Scratch 的能力](https://blog.just4fun.site/scratch-adapter-eim-script.html#_4)。
+- [Python对象的连接器:EIM 插件](/project_tutorial/eim_pt/)
+- [hello world(Adapter Extension)](/dev_guide/helloworld/)
+- [Adapter Node](/dev_guide/Adapter-Node/)
-## 如何找到 Adapter 主目录
+## 如何找到 Adapter 主目录(以Adapter 4.9.5为例)
Adapter 主目录,也是 Adapter 的日志目录,这儿存放了 Adapter 运行时使用的数据。
-Mac/Linux 用户的 Adapter 主目录在:`~/codelab_adapter`,如果找不到插件目录(如 Windows 用户),可以通过 CodeLab Adapter Web UI 工具栏里的`插件->打开插件目录`打开它。
+Windows 和 Mac,数据都在软件内部。
+
+* Windows: `codelab-adapter-4_9_5-win64/src/adapter_home`
+* Mac: `codelab-adapter-4_9_5-mac.app/Contents/Resources/adapter_home`
+* Linux: `~/codelab_adapter`
+
+如果找不到插件目录(如 Windows 用户),可以通过 CodeLab Adapter Web UI 工具栏里的`插件->打开插件目录`打开它。
## 如何找到插件目录
@@ -107,49 +116,29 @@ Windows 用户如果找不到用户配置文件目录,可以通过 CodeLab Ada
## 目前都支持哪些插件
-[extensions_v2](https://github.com/CodeLabClub/codelab_adapter_extensions/tree/master/extensions_v2)。
+[codelab_adapter_extensions](https://github.com/CodeLabClub/codelab_adapter_extensions)。
## 支持移动端吗(iPad/手机)
-支持。
-
-这是一个[例子](/video/codelab_ipad.mp4)。
-
-## 如何开机自启
-从 2.6.2 开始, 支持 headless 模式([点击下载 Raspbian 版本](/video/codelab-adapter-rpi-2_6_2.zip)),用于开机自启、无人值守的环境。
+Adapter 无法直接运行在移动端(可以运行在安卓的linux模拟器(如Termux)上)
-```
-chmod +x codelab-adapter
-./codelab-adapter --cli
-```
+在移动端上使用 Adapter 的方式是,将 Adapter 运行在计算机(如树莓派)上,之后通过url参数指向它: `https://scratch-beta.codelab.club/?adapter_host=192.168.31.140`
-推荐配置为:
+但由于不同平台对 https 的限制策略不同,可能需要一些处理技巧。
-```python
-# doc: https://adapter.codelab.club/user_guide/settings/
-OPEN_MESSAGE_HUB = True
-USE_SSL = False
-AUTO_OPEN_WEBUI = False
-PYTHON3_PATH = None
-DEFAULT_ADAPTER_HOST = "codelab-adapter.codelab.club"
-OPEN_WEBSOCKET_API = True
-OPEN_REST_API = False
-TOKEN = "ls3fb138c4124027"
-```
-
-之后打开 `http://raspberrypi.local:12358/?token=ls3fb138c4124027`
+这是一个[例子](/video/codelab_ipad.mp4)。
## 自定义存储目录
-2.6.3 开始支持这项功能。
使用环境变量`ADAPTER_HOME_PATH`来软件 home 目录,
例子:`ADAPTER_HOME_PATH=/tmp/my_adapter_home ./codelab-adapter --cli`
## 离线使用
-CodeLab Adapter 支持离线使用,目前有 2 种方式使用它。
+CodeLab Adapter 支持离线使用,目前有 3 种方式使用它。
- 1. 配合 [CodeLab Scratch Desktop(离线版)](https://www.codelab.club/blog/2020/08/20/tools/)使用。
- 2. 将 Web UI 里的`codelab-adapter.codelab.club`替换为`127.0.0.1`,形如 `https://codelab-adapter.codelab.club:12358/?token=YOUR_TOKEN`,重新刷新页面。
+1. (推荐) 修改host,添加一条`127.0.0.1 codelab-adapter.codelab.club` , [详情](#codelab-adapter_3)
+2. 配合 [CodeLab Scratch Desktop(离线版)](https://www.codelab.club/blog/2020/08/20/tools/)使用。
+3. 将 Web UI 里的`codelab-adapter.codelab.club`替换为`127.0.0.1`,形如 `https://codelab-adapter.codelab.club:12358/?token=YOUR_TOKEN`,重新刷新页面。
推荐使用`方法 1`。
@@ -157,7 +146,7 @@ CodeLab Adapter 支持离线使用,目前有 2 种方式使用它。
## 查看本地环境
-
+
你将看到:
@@ -175,20 +164,17 @@ CodeLab Adapter 支持离线使用,目前有 2 种方式使用它。
CodeLab Adapter 有很好的分布式支持:
-1. [Adapter Node](https://adapter.codelab.club/dev_guide/Adapter-Node/) 可以与 CodeLab Adapter 分布式协同
+1. [Adapter Node](/dev_guide/Adapter-Node/) 可以与 CodeLab Adapter 分布式协同
2. CodeLab Adapter 可以与 Scratch 分布式协同
为了解决 **树莓派里运行 Scratch 比较卡** 的问题,我们 可以让Adapter 运行在树莓派里,Scratch 则运行在本机上。
首先在树莓派中运行CodeLab Adapter,复制 WebUI 的URL, 形如: `https://codelab-adapter.codelab.club:12358/?token=765b3d2901ef47a0`
-
-
将 codelab-adapter.codelab.club 修改为 树莓派的 IP 地址: `https://192.168.21.104:12358/?token=765b3d2901ef47a0`。
现在你可以在 PC 里打开 树莓派里的 Adapter(需要安全校验) :
-
接着让我们在 CodeLab Scratch 里使用它, 打开: `https://scratch-beta.codelab.club/?adapter_host=192.168.21.104` (adapter_host 是 adapter 所在计算机的 IP,即树莓派的 IP, 它甚至可以运行在互联网的任何设备,任何位置!包括手机!)
@@ -204,11 +190,86 @@ CodeLab Adapter 有很好的分布式支持:
添加如下host规则:
-`codelab-adapter.codelab.club 127.0.0.1`
+`127.0.0.1 codelab-adapter.codelab.club`
以下是不同系统的hosts文件所在位置
* Windows: `C:\Windows\System32\drivers\etc\hosts`
* Android: `/system/etc/hosts`
* Mac/Linux: `/etc/hosts`
-* iPhone: `/etc/hosts`
\ No newline at end of file
+* iPhone: `/etc/hosts`
+
+## Windows 系统常见文件
+
+### 提示`缺少Python3`
+
+可能是你把 Adapter 当到了带有 **空格** 的文件夹里了。
+
+## 如何获取文件路径
+以下是windows 10 下的操作: `右键目标文件 -> 属性`
+
+
+
+## 为何有些网络下会无法Adapter发现/连接wifi设备
+这种情况,可能发生在wifi设备(悟空机器人、Romomaster EP)
+
+当你换一个网络就可用时, 那么问题可能是在某个网络启动Adapter,系统询问你是否允许其连接公共网络,你没有同意。
+
+可能的操作方法为: [Win10系统禁止/恢复某个程序连接网络的方法](https://jingyan.baidu.com/article/22a299b5e6fa909e18376a55.html)
+
+允许 adapter 内置的Python连网络(Python路径可在WebUI中看到)
+
+## 如何排查 无法发现设备 的问题?
+
+打开Adapter内置的 jupyterlab 插件,打开 `notebooks目录 > devices-connect-test.ipynb`,测试对应设备的连接,如果此处显示 **发现** 设备,但Adapter插件无法与设备连接,说明是Adapter插件问题,请[给我们反馈bug, 最好配上截图或视频](https://discuss.codelab.club/); 如果此处显示 **未发现** 设备,则与Adapter无关,请检查你的网络、USB连接、以及设备是否正常。
+
+## MacOS 应用程序没有响应
+如果你遇到以下情况
+
+
+
+可以通过以下方式重新打开Adapter: `右键 -> 显示包内容 -> Contents -> MacOS -> CodeLab-Adapter`
+
+
+
+点击运行目录里的 CodeLab-Adapter
+
+
+
+如果因此导致无法关机, `control + command + 电源键` 或者 `长按电源键`
+
+## webUI 无法退出Adapter (token错误)
+### MacOS
+
+
+### Windows
+图标在页面右下角, 右键退出。
+
+### Linux & RPI
+`pkill -f codelab-adapter`
+
+## 之前安装过旧版本的Adapter,使用新的版本出现问题
+
+下载最新的 Adapter
+
+
+
+
+
+## MacOS 11 无法扫描蓝牙设备
+现象: 使用蓝牙设备(诸如overdrive) ,系统会提示蓝牙未开启。
+
+原因: MacOs 权限管理更为严格,需要授权。
+
+解决方案: 从软件包里打开 Adapter,然后授权。
+
+可以通过以下方式重新打开Adapter: `右键 -> 显示包内容 -> Contents -> MacOS -> CodeLab-Adapter`
+
+
+
+点击运行目录里的 CodeLab-Adapter
+
+
+
+## windows 32bit 版本
+[winFull.zip(4_9_3)](https://scratch3-files.just4fun.site/codelab-adapter-4_9_3-win-fix.zip)
\ No newline at end of file
diff --git a/docs/user_guide/Linda.md b/docs/user_guide/Linda.md
new file mode 100644
index 0000000..5344707
--- /dev/null
+++ b/docs/user_guide/Linda.md
@@ -0,0 +1,379 @@
+# Linda
+
+
+
+用于协调不同的程序,使它们进行协作。
+
+!!! 提醒
+ 在 **Adapter >= 4.0** 中可用。
+
+
+
+# 介绍
+
+CodeLab Adapter 4.0 内置了 Linda server(Tuple Space),目前我们提供了以下客户端(持续增加中...)与 Linda Tuple Space 交互:
+
+- [Python Client](#python-client)
+- [Scratch Client](#scratch-client)
+- [REST API](#rest-api)
+- [cli (命令行客户端)](#cli)
+- [JavaScript Client(开发者)](#javascript-client)
+- [mush-lang](#mush-lang)
+
+Linda 最有趣的一个地方是,所有 Tuple Space 参与者(跨语言、跨系统、跨网络)都能够互操作,语义由参与者自己"协调", 所以 Alan Kay 将 Linda 称为"协调语言"。
+
+# 基本操作(operate)
+
+### 核心操作
+* out: 生成一个元组(tuple) 到 元组空间(tuple space)
+* in: 在tuple space中匹配元组,如果匹配到则消耗它, 如果未匹配则一直等待
+ * inp: in的非阻塞版本。 如果匹配到则消耗它, 如果未匹配则返回空元组
+* rd: read only, 在tuple space中匹配元组,如果匹配到则返回它(不移除), 如果未匹配则一直等待
+ * rdp: 非阻塞版本的 rd
+* eval: 暂不考虑实现
+
+### 辅助操作
+不在 linda 的原始论文中,是我自己的扩展
+
+* dump: 获取元组空间所有元组
+* status: 获取元组空间状态
+* reboot: 重置元组空间
+ * reboot 将重置元组空间,确保执行这个操作时,没有任何其他 linda client ,否则,此前的 linda 操作将一直处于等待中。
+
+
+# Python Client
+!!!提醒
+ 已经内置在完整版(Windows/macOS)里, 打开 Jupyterlab 即可使用。
+
+安装依赖: `pip install codelab_adapter_client`
+
+
+提供同步和异步两种基类:
+
+- AdapterNode
+- AdapterNodeAio
+
+## AdapterNode
+
+```python
+import time
+from codelab_adapter_client import AdapterNode
+
+class MyNode(AdapterNode):
+ NODE_ID = "linda/test"
+
+ def __init__(self):
+ super().__init__()
+
+node = MyNode()
+node.receive_loop_as_thread()
+time.sleep(0.1) # 等待zmq通信管道建立完成
+```
+
+创建Adapter Node之后,就可以通过node使用linda了。
+
+```python
+res = node.linda_reboot() # reboot linda server, clean tuple space
+assert res == []
+
+res = node.linda_out([1, 2, 3]) # out
+assert res == [1, 2, 3]
+
+res = node.linda_out([1, 2, 4]) # out
+res = node.linda_dump()
+assert res == [[1, 2, 3], [1, 2, 4]]
+
+res = node.linda_rd([1, 2, 3]) # read and blocking
+assert res == [1, 2, 3]
+
+res = node.linda_rdp([1, 2, "*"]) # read but non-blocking
+assert res == [1, 2, 3] # 先入先出
+
+res = node.linda_in([1,2,3]) # read then remove (blocking)
+assert res == [1, 2, 3]
+```
+
+## AdapterNodeAio(异步)
+
+同步和异步 API 保持一致
+
+```python
+import asyncio
+from codelab_adapter_client import AdapterNodeAio
+
+class MyNode(AdapterNodeAio):
+ NODE_ID = "linda/test"
+
+ def __init__(self):
+ super().__init__()
+
+# 以下代码在 jupyter 中运行,如果你想在python脚本中使用,请考虑异步代码的生命周期,参考: https://github.com/CodeLabClub/codelab_adapter_client_python/blob/master/tests/test_linda_client.py#L26
+node = MyNode()
+task = asyncio.create_task(node.receive_loop())
+await asyncio.sleep(0.1) # !! 等待zmq通信管道建立完成
+
+_tuple = ["test_linda"]
+
+# reboot
+res = await node.linda_reboot()
+assert res == []
+
+# out
+_tuple = ["hello", "world"]
+await node.linda_out(_tuple)
+
+# rdp
+res = await node.linda_rdp(_tuple)
+assert res == _tuple
+
+# inp
+res = await node.linda_inp(_tuple)
+assert res == _tuple
+
+res = await node.linda_dump()
+assert res == []
+```
+
+更多用法参考测试文件: [test_linda_client.py](https://github.com/CodeLabClub/codelab_adapter_client_python/blob/master/tests/test_linda_client.py)
+
+# Scratch Client
+
+
+
+
+
+# REST API
+
+使用 [httpie](https://httpie.io/docs#non-string-json-fields) 作为客户端。`:=` 表示后边跟的是 json 数据
+
+## out
+
+`http post https://codelab-adapter.codelab.club:12358/api/linda operate=out tuple:='["hello", "linda"]'`
+
+## in
+
+`http post https://codelab-adapter.codelab.club:12358/api/linda operate=in tuple:='["hello", "linda"]'`
+
+## dump
+
+`http post https://codelab-adapter.codelab.club:12358/api/linda operate=dump`
+
+其他原语类似
+
+## cli (命令行客户端)
+
+```bash
+pip install https://github.com/CodeLabClub/codelab_adapter_client_python/archive/master.zip
+# pip install codelab_adapter_client --upgrade # 暂未更新到 pypi
+codelab-linda --help
+
+codelab-linda out --help
+
+# reboot
+codelab-linda reboot
+
+# dump
+codelab-linda dump
+
+# out
+codelab-linda out --data '[1, "hello"]'
+
+# rd
+codelab-linda rd --data '[1, "hello"]'
+codelab-linda rd --data '[1, "*"]'
+
+# rdp
+codelab-linda rd --data '[1, 2, 3]' # []
+
+# in
+codelab-linda rd --data '[1, "*"]'
+```
+
+# JavaScript Client
+方便开发者,将 Linda 引入自己的web项目。
+
+CodeLab 目前使用 JavaScript Client,将 Linda 带入 CodeLab Scratch、CodeLab Adapter WebUI 和 Lively。
+
+```js
+import AdapterBaseClient from "./codelab_adapter_base.js"; // https://github.com/CodeLabClub/scratch3_eim/blob/v3/codelab_adapter_base.js
+let NODE_ID = "linda/js/client";
+let HELP_URL = "https://adapter.codelab.club/user_guide/Linda/";
+let runtime = null;
+let adapter_client = new AdapterBaseClient(NODE_ID, HELP_URL, runtime);
+
+await adapter_client.linda_out([1,2,3]).then((data)=>{console.log("linda",data); return data})
+
+tuple = await adapter_client.linda_in(["hi", "lively", "*"]).then((data)=>{console.log("linda",data); return data})
+
+tuple = await adapter_client.linda_in(["hi", "python", "from Lively"]).then((data)=>{console.log("linda",data); return data})
+
+tuple = await adapter_client.linda_in(["hello", "lively", "*"]).then((data)=>{console.log("linda",data); return data})
+
+await adapter_client.linda_in([1,2,5], 1000).then((data)=>{console.log("linda",data); return data}) //超时
+```
+
+
+
+# mush-lang
+> LISP 是一种构建材料 -- Alan Kay
+
+为了更好地探索 Linda 的可能性,我们围绕 Linda 的基本原语,构建了一门简单的语言 -- [**mush-lang**](https://github.com/wwj718/mush-lang)。
+
+mush-lang 采用 LISP 风格的语法,可以视为 LISP 的一门玩具方言。 LISP 因其同构性(内外表示一致),可能是所有语言中最简单的。
+
+mush-lang 目前在 Python 中实现。
+
+
+
+# Demo
+
+
+## 多个 Scratch 角色 的 实时同步
+
+
+
+在 Python 的例子中,我们甚至在Scratch里构建了 Server!
+
+
+两个Scratch角色同步的代码如下
+
+* [linda-demo1](https://scratch-beta.codelab.club/?sb3url=https://adapter.codelab.club/sb3/linda-demo1.sb3)
+* [linda-demo2](https://scratch-beta.codelab.club/?sb3url=https://adapter.codelab.club/sb3/linda-demo2.sb3)
+
+
+python 与 Scratch 同步的代码如下:
+
+* [linda-demo1](https://scratch-beta.codelab.club/?sb3url=https://adapter.codelab.club/sb3/linda-demo1.sb3)
+
+Python核心部分代码为:
+
+```python
+node.linda_out(["request", "loudness", "xxx"])
+node.linda_in(["response", "loudness", "*"])
+```
+
+
+## Jupyter 与 Scratch 的互操作
+
+
+跨语言对象之间的互操作
+
+用到了 jupyterlab 3.0 里的 ipywidgets.
+
+```python
+# 在jupyterlab 3.0中可用
+from ipywidgets import interact, interactive, fixed, interact_manual
+from codelab_adapter_client import AdapterNode
+import time
+
+class MyNode(AdapterNode):
+ NODE_ID = "linda/jupyter"
+
+ def __init__(self):
+ super().__init__()
+
+node = MyNode()
+node.receive_loop_as_thread()
+
+@interact(show=True, x=100, size=100)
+def f(show,x,size):
+ node.linda_out(["%%x", x], wait=False) # f函数是非阻塞的回调函数,使用wait=False参数,使node.linda_out使非阻塞的,此时相当于流,记得使用 message tuple(见下文)
+ node.linda_out(["%%show", show], wait=False)
+ node.linda_out(["%%size", size], wait=False)
+ return show,x,size
+```
+
+
+# 进阶
+## 消息风格
+linda 的基本观点是数据不停生灭(由用户显式操控)。
+
+如果我们想在 Linda 中实现 "消息/流" 的模式,可能会遇到tuple堆积(生产者太快)的问题(这是很严重的问题,似乎也不是正确使用linda的方式)
+
+为了尽可能少地破坏概念完整性,我们引入了一种特殊的tuple来支持"消息/流"模式。
+
+我们定义了一种叫做 message tuple 的 tuple,它像消息一样,每次只能流的瞬时截面: **一个数据**。
+
+以下是几个message tuple的例子:
+
+* ("%%x", 1)
+* ("%%y", 50)
+* ("%%z", "hello", "world")
+
+在语法层面,message tuple只是普通的tuple,唯一区别是第一个元素需要是如下风格字符串, "%%x", x可以是任意值,可以把它看作message tuple的id,不同id的message tuple被视为不同tuple,支持tuple的所有操作符。
+
+以下是一个例子:
+
+
+* [message_tuple demo](https://scratch-beta.codelab.club/?sb3url=https://adapter.codelab.club/sb3/linda-message_tuple.sb3)
+
+
+!!!视角
+ 站在变量的视角,你可以将其看作全局变量
+
+
+# FAQ
+
+## 如何看到 Linda Tuple Space
+`Adapter >=4.1.0`
+
+
+
+
+
+## 在 Scratch 里有些 **in/rd** 积木一直阻塞
+简单而言,按照以下顺序运行程序:
+
+* 确保在linda in/rd 积木运行之前,先运行linda reboot
+* 之后在启动Scratch程序
+
+以下是原因分析(可以不看):
+
+这个Linda背后的实现有关,Adapter Linda 目前是C/S架构。Scratch中的 in/rd 积木实际上 promise。
+
+reboot针对的是linda server的操作。
+
+如果程序在 in/rd 的时候,被reboot,则客户端(Scratch)的 in/rd 对应的promise永远不会被解决。
+
+
+**linda reboot** 一下
+
+## 速度
+
+
+
+默认情况下,30帧/s。
+
+在Python客户端,通过修改参数,可以提高到300-600帧/s。
+
+```python
+class MyNode(AdapterNode):
+ NODE_ID = "linda/test"
+
+ def __init__(self):
+ super().__init__(recv_mode="block", bucket_fill_rate=1000, bucket_token=1000)
+```
+
+
+## Linda 与 EIM
+Linda 与 EIM 将长期共存,一个 Adapter Node,即是Linda client,也是EIM client,它们各有所擅。长期来看,我们更偏好 Linda。
+
+
+
+---
+
+
+
+# 参考
+
+- [在 CodeLab Adapter 中实现 Linda 并发模型](https://wwj718.github.io/post/%E7%BC%96%E7%A8%8B/adapter-linda/)
+- [Linda: 比 Actor 更好的并发模型](https://wwj718.github.io/post/%E7%BC%96%E7%A8%8B/linda-intro/)
+- [[译]Alan Kay 看待'对象'的几次观点转变](https://wwj718.github.io/post/%E7%BC%96%E7%A8%8B/alan-key-between-oo-fp/)
+- [建立在异步消息之上的同步指令: 分别在 JavaScript、Python、Squeak 上实现](https://wwj718.github.io/post/%E7%BC%96%E7%A8%8B/async-msg-sync-cmd/)
+- [[译]Smalltalk 背后的设计原则](https://wwj718.github.io/post/%E7%BC%96%E7%A8%8B/design-principles-behind-smalltalk/)
diff --git a/docs/user_guide/PRO_KEY.md b/docs/user_guide/PRO_KEY.md
new file mode 100644
index 0000000..7399a5a
--- /dev/null
+++ b/docs/user_guide/PRO_KEY.md
@@ -0,0 +1,9 @@
+# PRO_KEY
+PRO_KEY 是 CodeLab 尝试服务合作伙伴和企业用户的高级特性。
+
+使用 PRO_KEY 可以启用 Adapter 的高级特性: 激光雷达、overdrive...
+
+## 如何获取 PRO_KEY
+欢迎发送邮件咨询/合作/购买: `wuwenjie@codelab.club`
+
+# FAQ
diff --git a/docs/user_guide/develop.md b/docs/user_guide/develop.md
index 6a8c49b..77fae8c 100644
--- a/docs/user_guide/develop.md
+++ b/docs/user_guide/develop.md
@@ -1,6 +1,8 @@
# 测试版
+
\ No newline at end of file
diff --git a/docs/user_guide/gallery-bak.md b/docs/user_guide/gallery-bak.md
index 02ffff0..ffea337 100644
--- a/docs/user_guide/gallery-bak.md
+++ b/docs/user_guide/gallery-bak.md
@@ -54,7 +54,7 @@
## 在 CodeLab Scratch3 中,使用 micro:bit 开/关灯
-参考[积木化编程与智能家居](https://blog.just4fun.site/scratch3-smart-home.html)。
+参考[积木化编程与智能家居](https://wwj718.github.io/scratch3-smart-home.html)。
## 在 CodeLab Scratch3 中,利用运动检测开/关灯
@@ -62,7 +62,7 @@
### 在 CodeLab Scratch3 中,利用传感器做体感游戏
-参考[积木化编程与智能家居](https://blog.just4fun.site/scratch3-smart-home.html)。
+参考[积木化编程与智能家居](https://wwj718.github.io/scratch3-smart-home.html)。
## 在 CodeLab Scratch3 中,使用本地的机器视觉
@@ -73,25 +73,25 @@
我们布置好了一个可编程的空间,使用 Scratch3 中的积木进行空间编程,控制室内的灯光、插座、空调、电视,如果你愿意,也可以去淘宝买一根哈利波特的木制魔杖(估计只要九块九),用我们提供的机器视觉积木训练一下,就可以用你自制的咒语(比如用魔杖划一个o)控制你的房间
-参考[积木化编程与智能家居](https://blog.just4fun.site/scratch3-smart-home.html)。
+参考[积木化编程与智能家居](https://wwj718.github.io/scratch3-smart-home.html)。
## 门与电视的联动
我们使用树莓派可将任何屏幕变为变为一个可编程的广告牌(哈哈只要 200 块哦,这块似乎也值得商业化[奸笑])。加上简单的神经网络你可以做到,当穿着裙子的姑娘走近屏幕,屏幕立马给她安利新款纪梵希,而穿着格子衬衫的大叔靠近时,屏幕则以机械键盘蛊惑他。
-参考[积木化编程与智能家居](https://blog.just4fun.site/scratch3-smart-home.html)。
+参考[积木化编程与智能家居](https://wwj718.github.io/scratch3-smart-home.html)。
## 纸做的开关
-参考[积木化编程与智能家居](https://blog.just4fun.site/scratch3-smart-home.html)。
+参考[积木化编程与智能家居](https://wwj718.github.io/scratch3-smart-home.html)。
## Cozmo 接入在线课程平台
-参考[使用 Codelab_Adapter 连接 blockly 与硬件](https://blog.just4fun.site/scratch3_adapter-blockly.html)。
+参考[使用 Codelab_Adapter 连接 blockly 与硬件](https://wwj718.github.io/scratch3_adapter-blockly.html)。
## Incendio(火焰咒)
@@ -101,7 +101,7 @@
-参考[codelab-adapter 接入优必选 Alpha 系列机器人](https://blog.just4fun.site/scratch3-adapter-ubtrobot.html)
+参考[codelab-adapter 接入优必选 Alpha 系列机器人](https://wwj718.github.io/scratch3-adapter-ubtrobot.html)
## 接入 blender(连接游戏、电影与 VR)
@@ -345,11 +345,11 @@
-### [震动检测器](https://www.codelab.club/blog/vibration-detectors/)
+### [震动检测器](https://www-old.codelab.club/blog/vibration-detectors/)
-### [生日贺卡](https://www.codelab.club/blog/dynamictable-happybirthday/)
+### [生日贺卡](https://www-old.codelab.club/blog/dynamictable-happybirthday/)
diff --git a/docs/user_guide/install.md b/docs/user_guide/install.md
index 085c882..75a60e0 100644
--- a/docs/user_guide/install.md
+++ b/docs/user_guide/install.md
@@ -1,5 +1,6 @@
-# 安装(install)
+[安装(install)](/get_start/gs_install?version=latest)
+
## 兼容性
@@ -47,6 +46,8 @@ Linux 测试了
Raspbian 我们只测试了最新版本,如果有系统兼容性问题,欢迎[联系我们](/about/contact/)。
+-->
+
+
+## KEEP_LAST_CLIENT
+是否强行保留最后一个web client(webUI 或 Scratch)
+
+默认是 `False`
+
+## ALWAYS_KEEP_ADAPTER_RUNNING
+当所有web client退出时, Adapter 软件是否也退出
+
+默认是 `False`
+
## OPEN_MESSAGE_HUB
@@ -37,20 +56,7 @@ CodeLab Adapter 允许用户的自定义配置。配置文件位于:`~/codelab_a
## PYTHON3_PATH
-系统的 Python3 路径,当使用 [CodeLab Adapter Node](https://github.com/CodeLabClub/codelab_adapter_extensions/tree/master/nodes_v3) 时,实际上是调用了使用了系统的 Python3。
-
-PYTHON3_PATH 默认为:
-
-```python
- if (platform.system() == "Darwin"):
- path = "/usr/local/bin/python3"
- if platform.system() == "Windows":
- path = "python"
- if platform.system() == "Linux":
- path = "/usr/bin/python3"
-```
-
-你可以自行指定。
+系统的 Python3 路径, 只在 lite 版本(linux)中可用,默认为 `"/usr/bin/python3"`, 你可以自行指定。
## DEFAULT_ADAPTER_HOST
@@ -77,3 +83,35 @@ OPEN_REST_API 用于打开/关闭 [REST API](/dev_guide/REST-API/)。
取消注释`# TOKEN = "