@blackwalnutlab/jupyterlab_micropython_ext v0.1.0
Jupyterlab Micropython Extension
一个连接 MicroPython
开发板的 Jupyter Lab
插件. 目前仅仅在 Windows
和 Ubuntu 18.04
系统下测试可用.
插件依靠 JupyterLab MicroPython Kernel
与 MicroPython
通讯, 使用 JupyterLab MicroPython Kernel
的 Jupyter Lab
可以通过输入 %serialconnect --port=/dev/ttyUSB0
来连接:
插件的不同按钮对应了不同的 magic command
以此实现通过 UI
与 MicroPython
开发板交互.
Requirements
- JupyterLab >= 2.0.1
- jupyterlab_micropython_kernel >= 0.0.1
Install
Install JupyterLab MicroPython Kernel
这个插件是通过 JupyterLab MicroPython Kernel
与 MicroPython
交互的. 所以要使用插件需要首先安装 JupyterLab MicroPython Kernel
首先下载 JupyterLab MicroPython Kernel
到本地, 并在 bash
中切换到项目路径:
git clone https://github.com/zhouzaihang/jupyterlab_micropython_kernel.git
cd jupyterlab_micropython_kernel
然后使用 pip
安装它 作为一个 Python3
的库, 使用 -e
参数表示在可编辑模式(editable mode
)下从一个本地的项目路径或 VCS URL
中安装一个项目:
pip install -e .
在 python/../site-packages
(Python
默认库的安装位置) 会创建一个文件指向到当前目录, 当你使用 git pull
或者其他操作修改代码后, 会自动调用最新的代码.(如果出错的话请检查你使用 Python
环境下是否应该使用 pip3
, sudo pip
等)
然后输入以下命令把 kernelspec
添加到 jupyter
中:
python -m jupyterlab_micropython_kernel.install
这会创建文件 .local/share/jupyter/kernels/micropython/kernel.json
, 并指向当前安装的 kernelspec
.
你可以使用以下命令查看当前所有安装的 kernelspec
极其位置:
jupyter kernelspec list
Install Extension
首先下载 JupyterLab MicroPython Ext
到本地, 并在 bash
中切换到项目路径:
# Clone the repo to your local environment
git clone https://github.com/zhouzaihang/jupyterlab_micropython_ext.git
# Move to jupyterlab_micropython_ext directory
cd jupyterlab_micropython_ext
jlpm
命令是一个 Jupyter Lab
使用的 yarn 固定版本, yarn会在安装 Jupyter Lab
的时候一起安装. 下面的命令中你也可以使用
yarn
或 npm
替换 jlpm
.
# Install dependencies
jlpm
# Build Typescript source
jlpm build
# Link your development version of the extension with JupyterLab
jupyter labextension link .
当项目有更新时, 你可以运行以下命令更新代码并 rebuild
插件和 Jupyter Lab
程序.
# Update source code
git pull
# Rebuild Typescript source after making changes
jlpm build
# Rebuild JupyterLab after making any changes
jupyter lab build
在开发过程中, 你可以监听文件的变化, 然后在文件修改后自动 rebuild
插件和 Jupyter Lab
程序.
# Watch the source directory in another terminal tab
jlpm watch
# Run jupyterlab in watch mode in one terminal tab
jupyter lab --watch
Development
搭建开发环境可以见 Jupyter Lab 官方文档
打开 src/index.ts
, 找到 extension: JupyterFrontEndPlugin<void>
, 修改其中的 activate
:
/**
* The plugin registration information.
*/
const extension: JupyterFrontEndPlugin<void> = {
id: 'jupyterlab_micropython_ext',
autoStart: true,
activate: activate
};
编写 activate
函数:
/**
* Initialization data for the jupyterlab_micropython_ext extension.
*/
const activate = async (app: JupyterFrontEnd) => {
app.docRegistry.addWidgetExtension('Notebook', new MicropythonExtension())
console.log('JupyterLab extension jupyterlab_micropython_ext is activated!');
}
然后编写在 activate
中调用的 MicropythonExtension
类:
/**
* A notebook widget extension that adds a Extension to the toolbar.
*/
export
class MicropythonExtension implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel> {
/**
* Create a new extension object.
*/
createNew(panel: NotebookPanel, context: DocumentRegistry.IContext<INotebookModel>): IDisposable {
return new DisposableDelegate(() => {});
}
}
接下来需要在面板的工具栏中添加按钮, 首先在 MicropythonExtension
的 createNew
方法中创建一个 ToolbarButton
:
let serialConnectButton = new ToolbarButton({
className: 'serialConnect',
iconClassName: 'fa fa-cloud-plug',
onClick: () => {
console.log("Click ToolbarButton");
},
tooltip: 'Connect device with serialport'
});
把创建好的按钮插入到 toolbar
中:
panel.toolbar.insertItem(8, 'Serial Connect', serialConnectButton)
最后要记得在 DisposableDelegate
中 dispose
按钮:
return new DisposableDelegate(() => {
uploadFolderButton.dispose();
});
最后编写按钮的业务逻辑, 把按钮的点击事件修改为一个异步函数:
let serialConnectCallback = async () => {
// TODO
};
let serialConnectButton = new ToolbarButton({
className: 'serialConnect',
iconClassName: 'fa fa-plug',
onClick: serialConnectCallback,
tooltip: 'Connect device with serialport'
});
使用 InputDialog
可以设置一个弹出的输入框, 接收输入数据. 使用 input.button.accept
判断是否收到输入:
const input = await InputDialog.getText({
title: 'Serial Port',
okLabel: 'Connect',
placeholder: '/dev/ttyUSB0'
});
if (input.button.accept) {
// TODO
}
使用 context.session.kernel
可以获取当前运行的 kernel
, 然后通过 kernel.requestExecute()
方法给 kernel
发送要执行的语句:
const port = input.value;
const code: string = `%serialconnect --port=${port}`;
const content: KernelMessage.IExecuteRequestMsg['content'] = {
code,
stop_on_error: true
};
kernelrequestExecute(content);
context.session.kernel.requestExecute(content, false);
kernel.requestExecute
执行后会返回的内容是异步的, 可以使用 done
监听是否执行完成, 使用 onIOPub
监听收到的返回数据:
let future = context.session.kernel.requestExecute(content, false);
let result = "";
future.onIOPub = msg => {
if ('name' in msg.content && msg.content.name === 'stdout' && msg.content.text !== undefined) {
result = `${result}${msg.content.text.replace(/\u001b\[\d+m/g, '')}. `
}
};
future.done.then(() => {
showErrorMessage("Kernel Execute Result", result.toString());
});
因为 kernel
返回数据是 ANSI Escape sequences
, 所以使用正则表达式 \u001b\[\d+m
过滤: msg.content.text.replace(/\u001b\[\d+m/g, '')
最终的代码如下:
/**
* A notebook widget extension that adds a Extension to the toolbar.
*/
export
class MicropythonExtension implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel> {
/**
* Create a new extension object.
*/
createNew(panel: NotebookPanel, context: DocumentRegistry.IContext<INotebookModel>): IDisposable {
let kernelrequestExecute = (content: KernelMessage.IExecuteRequestMsg['content']) => {
let future = context.session.kernel.requestExecute(content, false);
let result = "";
future.onIOPub = msg => {
if ('name' in msg.content && msg.content.name === 'stdout' && msg.content.text !== undefined) {
result = `${result}${msg.content.text.replace(/\u001b\[\d+m/g, '')}. `
}
};
future.done.then(() => {
showErrorMessage("Kernel Execute Result", result.toString());
});
}
let serialConnectCallback = async () => {
const input = await InputDialog.getText({
title: 'Serial Port',
okLabel: 'Connect',
placeholder: '/dev/ttyUSB0'
});
if (input.button.accept) {
const port = input.value;
const code: string = `%serialconnect --port=${port}`;
const content: KernelMessage.IExecuteRequestMsg['content'] = {
code,
stop_on_error: true
};
kernelrequestExecute(content);
}
};
let serialConnectButton = new ToolbarButton({
className: 'serialConnect',
iconClassName: 'fa fa-plug',
onClick: serialConnectCallback,
tooltip: 'Connect device with serialport'
});
panel.toolbar.insertItem(8, 'Serial Connect', serialConnectButton)
return new DisposableDelegate(() => {
serialConnectButton.dispose();
});
}
}
/**
* Initialization data for the jupyterlab_micropython_ext extension.
*/
const activate = async (app: JupyterFrontEnd) => {
app.docRegistry.addWidgetExtension('Notebook', new MicropythonExtension())
console.log('JupyterLab extension jupyterlab_micropython_ext is activated!');
}
/**
* The plugin registration information.
*/
const extension: JupyterFrontEndPlugin<void> = {
id: 'jupyterlab_micropython_ext',
autoStart: true,
activate: activate
};
export default extension;
Uninstall
运行以下命令卸载插件:
jupyter labextension uninstall jupyterlab_micropython_ext
TODO
Add button click event run result display.- 修改弹出窗口提示
UI
- fix bug.
4 years ago