0.1.0 • Published 1 year ago

@blackwalnutlab/jupyterlab_micropython_ext v0.1.0

Weekly downloads
-
License
BSD-3-Clause
Repository
github
Last release
1 year ago

Jupyterlab Micropython Extension

一个连接 MicroPython 开发板的 Jupyter Lab 插件. 目前仅仅在 WindowsUbuntu 18.04 系统下测试可用.

button_demo

插件依靠 JupyterLab MicroPython KernelMicroPython 通讯, 使用 JupyterLab MicroPython KernelJupyter Lab 可以通过输入 %serialconnect --port=/dev/ttyUSB0 来连接:

jupyterlab_micropython_kernel

插件的不同按钮对应了不同的 magic command 以此实现通过 UIMicroPython 开发板交互.

Requirements

  • JupyterLab >= 2.0.1
  • jupyterlab_micropython_kernel >= 0.0.1

Install

Install JupyterLab MicroPython Kernel

这个插件是通过 JupyterLab MicroPython KernelMicroPython 交互的. 所以要使用插件需要首先安装 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 的时候一起安装. 下面的命令中你也可以使用 yarnnpm 替换 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(() => {});
    }
}

接下来需要在面板的工具栏中添加按钮, 首先在 MicropythonExtensioncreateNew 方法中创建一个 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)

最后要记得在 DisposableDelegatedispose 按钮:

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

  1. Add button click event run result display.
  2. 修改弹出窗口提示 UI
  3. fix bug.