├── rpyc_ikernel ├── __init__.py ├── __main__.py ├── compat.py ├── scheduler.py ├── install.py ├── adb.py └── kernel.py ├── MANIFEST.in ├── tests ├── test-output │ └── .scripttest-test-dir.txt ├── images │ ├── index.png │ └── kernels.png ├── test_extract_uuid.py ├── test_rpyc.py ├── usage_connect.ipynb ├── test_cli.py ├── test_command_fix.py ├── test_ugly-code.ipynb ├── test_all.ipynb └── usage_display.ipynb ├── .gitignore ├── tox.ini ├── setup.py ├── README_cn.md └── README.md /rpyc_ikernel/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | -------------------------------------------------------------------------------- /tests/test-output/.scripttest-test-dir.txt: -------------------------------------------------------------------------------- 1 | placeholder -------------------------------------------------------------------------------- /tests/images/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sipeed/rpyc_ikernel/HEAD/tests/images/index.png -------------------------------------------------------------------------------- /tests/images/kernels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sipeed/rpyc_ikernel/HEAD/tests/images/kernels.png -------------------------------------------------------------------------------- /rpyc_ikernel/__main__.py: -------------------------------------------------------------------------------- 1 | """ 2 | rpyc ikernel entry point. 3 | 4 | From here you can get to 'manage', otherwise it is assumed 5 | that a kernel is required instead and instance one instead. 6 | """ 7 | 8 | from ipykernel.kernelapp import IPKernelApp 9 | from .kernel import RPycKernel 10 | 11 | IPKernelApp.launch_instance(kernel_class=RPycKernel) 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python cruft 2 | __pycache__/ 3 | *.py[cod] 4 | *.egg-info/ 5 | *.egg 6 | pip-wheel-metadata 7 | build/ 8 | dist/ 9 | 10 | # IDE cruft 11 | .idea 12 | .vscode 13 | *.swp 14 | *.orig 15 | .venv/ 16 | venv 17 | *.ps1 18 | 19 | # Testing 20 | .pytest_cache/ 21 | .tox 22 | *.db 23 | # pytest -> junit 24 | *.xml 25 | .cache 26 | .ipynb_checkpoints 27 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Tox (http://tox.testrun.org/) is a tool for running tests 2 | # in multiple virtualenvs. This configuration file will run the 3 | # test suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | 6 | [tox] 7 | envlist = py27, py38 8 | 9 | [testenv] 10 | passenv = LC_ALL, LANG 11 | commands = py.test 12 | deps = 13 | pytest 14 | scripttest 15 | -------------------------------------------------------------------------------- /rpyc_ikernel/compat.py: -------------------------------------------------------------------------------- 1 | """ 2 | compat.py 3 | 4 | Compatibility layer for transitions from IPython 3.0 to 5 | Jupyter structure. Will attempt to import Jupyter versions 6 | of modules, but will fall back to IPython if it is not 7 | available. 8 | 9 | Provides the following modules: 10 | kernelspec -> {jupyter_client,IPython}.kernel.kernelspec 11 | tempdir -> {tempfile,IPython.utils.tempdir} 12 | 13 | """ 14 | # This is a module copied from Python 3.2, so will exist 15 | # in 3.2 onwards 16 | import tempfile as tempdir 17 | 18 | # kernelspec is moved in jupyter 19 | try: 20 | from jupyter_client import kernelspec 21 | except ImportError: 22 | from IPython.kernel import kernelspec 23 | 24 | # Otherwise it might be in genutils, but that will be dissolved 25 | if not hasattr(tempdir, "TemporaryDirectory"): 26 | try: 27 | from ipython_genutils import tempdir 28 | except ImportError: 29 | from IPython.utils import tempdir 30 | 31 | __all__ = ("kernelspec", "tempdir") 32 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # default to setuptools so that 'setup.py develop' is available, 2 | # but fall back to standard modules that do the same 3 | try: 4 | from setuptools import setup 5 | 6 | scripts = [] 7 | except ImportError: 8 | from distutils.core import setup 9 | 10 | scripts = ["bin/rpyc_ikernel"] 11 | 12 | setup( 13 | name="rpyc_ikernel", 14 | version="0.5.1", 15 | description="rpyc for jupyter kernel", 16 | long_description=open('README.md', 'r', encoding='UTF-8').read(), 17 | long_description_content_type='text/markdown', 18 | author="Juwan", 19 | author_email="juwan@sipeed.com", 20 | license="BSD", 21 | url="https://github.com/sipeed/rpyc_ikernel", 22 | packages=["rpyc_ikernel"], 23 | # scripts=scripts, 24 | # entry_points={"console_scripts": ["rpyc_ikernel = rpyc_ikernel.__main__:main"]}, 25 | install_requires=["notebook", "pexpect", "rpyc", "pillow"], 26 | tests_requires=["pytest", "scripttest"], 27 | include_package_data=True, 28 | classifiers=[ 29 | "Programming Language :: Python :: 3", 30 | "Framework :: IPython", 31 | "License :: OSI Approved :: BSD License", 32 | ], 33 | ) 34 | -------------------------------------------------------------------------------- /tests/test_extract_uuid.py: -------------------------------------------------------------------------------- 1 | """ 2 | test_extract_uuid.py 3 | 4 | Check that the uuid can be extracted from filenames when 5 | available. 6 | 7 | """ 8 | 9 | from uuid import UUID 10 | 11 | from rpyc_ikernel.kernel import extract_uuid, RemoteIKernel 12 | 13 | 14 | def test_extract_uuid(): 15 | """Check that it gets the right one.""" 16 | this_uuid = UUID("fbbdec0f-1403-48c9-b2ae-b5b5e1572068") 17 | 18 | full_path = ( 19 | "/home/user/.local/share/jupyter/runtime/" 20 | "kernel-fbbdec0f-1403-48c9-b2ae-b5b5e1572068.json" 21 | ) 22 | relative_path = ( 23 | "../../.local/share/jupyter/runtime/" 24 | "kernel-fbbdec0f-1403-48c9-b2ae-b5b5e1572068.json" 25 | ) 26 | no_path = "kernel-fbbdec0f-1403-48c9-b2ae-b5b5e1572068.json" 27 | 28 | assert extract_uuid(full_path) == this_uuid 29 | assert extract_uuid(relative_path) == this_uuid 30 | assert extract_uuid(no_path) == this_uuid 31 | 32 | 33 | def test_no_uuid(): 34 | """Check that it gets nothing from these.""" 35 | full_path = "/home/user/.local/share/jupyter/runtime/" "kernel-not-a-uuid.json" 36 | relative_path = "../../.local/share/jupyter/runtime/" "kernel-not-a-uuid.json" 37 | no_path = "kernel-not-a-uuid.json" 38 | 39 | assert extract_uuid(full_path) is None 40 | assert extract_uuid(relative_path) is None 41 | assert extract_uuid(no_path) is None 42 | 43 | 44 | def test_kernel_uuid(): 45 | """Check that any kernel gets a uuid.""" 46 | this_uuid = UUID("fbbdec0f-1403-48c9-b2ae-b5b5e1572068") 47 | no_path = "kernel-fbbdec0f-1403-48c9-b2ae-b5b5e1572068.json" 48 | 49 | test_kernel = RemoteIKernel(connection_info=no_path, interface="test") 50 | assert test_kernel.uuid == this_uuid 51 | 52 | test_kernel = RemoteIKernel(connection_info="not-a-uuid.json", interface="test") 53 | assert test_kernel.uuid is not None 54 | assert isinstance(test_kernel.uuid, UUID) 55 | -------------------------------------------------------------------------------- /tests/test_rpyc.py: -------------------------------------------------------------------------------- 1 | import os 2 | import rpyc 3 | import tempfile 4 | from rpyc.utils.server import ThreadedServer, ThreadPoolServer 5 | from rpyc import SlaveService 6 | import unittest 7 | 8 | 9 | class Test_ThreadedServer(unittest.TestCase): 10 | 11 | def setUp(self): 12 | self.server = ThreadedServer(SlaveService, port=18878, auto_register=False) 13 | self.server.logger.quiet = False 14 | self.server._start_in_thread() 15 | 16 | def tearDown(self): 17 | while self.server.clients: 18 | pass 19 | self.server.close() 20 | 21 | def test_connection(self): 22 | conn = rpyc.classic.connect("localhost", port=18878) 23 | print(conn.modules.sys) 24 | print(conn.modules["xml.dom.minidom"].parseString("")) 25 | conn.execute("x = 5") 26 | self.assertEqual(conn.namespace["x"], 5) 27 | self.assertEqual(conn.eval("1+x"), 6) 28 | conn.close() 29 | 30 | 31 | class Test_ThreadedServerOverUnixSocket(unittest.TestCase): 32 | 33 | def setUp(self): 34 | self.socket_path = tempfile.mktemp() 35 | self.server = ThreadedServer(SlaveService, socket_path=self.socket_path, auto_register=False) 36 | self.server.logger.quiet = False 37 | self.server._start_in_thread() 38 | 39 | def tearDown(self): 40 | self.server.close() 41 | os.remove(self.socket_path) 42 | 43 | def test_connection(self): 44 | c = rpyc.classic.unix_connect(self.socket_path) 45 | print(c.modules.sys) 46 | print(c.modules["xml.dom.minidom"].parseString("")) 47 | c.execute("x = 5") 48 | self.assertEqual(c.namespace["x"], 5) 49 | self.assertEqual(c.eval("1+x"), 6) 50 | c.close() 51 | 52 | 53 | class Test_ThreadPoolServer(Test_ThreadedServer): 54 | 55 | def setUp(self): 56 | self.server = ThreadPoolServer(SlaveService, port=18878, auto_register=False) 57 | self.server.logger.quiet = False 58 | self.server._start_in_thread() 59 | 60 | 61 | if __name__ == "__main__": 62 | unittest.main() 63 | -------------------------------------------------------------------------------- /rpyc_ikernel/scheduler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | TIMEFORMAT = '%Y-%m-%d %H:%M:%S %Z' 5 | 6 | import threading 7 | import time 8 | 9 | 10 | class Scheduler(threading.Thread): 11 | 12 | def __init__(self, trigger, interval, fn, args=(), kwargs={}): 13 | super().__init__() 14 | if trigger not in ['delay', 'recur']: 15 | raise ValueError('trigger must be "delay" or "recur"') 16 | self.trigger = trigger 17 | self.interval = interval 18 | self.fn = fn 19 | self.args = args 20 | self.kwargs = kwargs 21 | self._event = threading.Event() 22 | self._stopped = True 23 | self._result = [] 24 | 25 | # Scheduler constructor 26 | @classmethod 27 | def create(cls, **scheduler_kwargs): 28 | return cls(**scheduler_kwargs) 29 | 30 | @property 31 | def stopped(self): 32 | return self._stopped 33 | 34 | @property 35 | def result(self): 36 | return dict(self._result) 37 | 38 | def run(self): 39 | self._stopped = self._event.is_set() # False 40 | if self.trigger == 'delay': 41 | self._delay() 42 | elif self.trigger == 'recur': 43 | self._recur() 44 | 45 | def _delay(self): 46 | if not self._stopped: 47 | self._event.wait(timeout=self.interval) 48 | self._execute() 49 | 50 | def _recur(self): 51 | while not self._stopped: 52 | self._execute() 53 | self._event.wait(timeout=self.interval) 54 | 55 | def _execute(self): 56 | self._fn(self.args, self.kwargs) 57 | 58 | def _fn(self, args, kwargs): 59 | return_value = self.fn(*args, **kwargs) 60 | if return_value: 61 | timestamp = time.strftime(TIMEFORMAT, time.localtime()) 62 | self._result.append( (timestamp, return_value) ) 63 | 64 | # threads can only be started once 65 | # i.e., cannot start again once cancelled 66 | def cancel(self): 67 | self._event.set() 68 | self._stopped = self._event.is_set() # True 69 | -------------------------------------------------------------------------------- /rpyc_ikernel/install.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import sys 4 | import argparse 5 | 6 | from jupyter_client.kernelspec import KernelSpecManager 7 | from IPython.utils.tempdir import TemporaryDirectory 8 | 9 | # copied out from https://github.com/takluyver/bash_kernel/blob/master/bash_kernel/install.py 10 | 11 | # sys.executable should be "python3" 12 | kernel_json = { 13 | "argv": [sys.executable, "-m", "rpyc_ikernel", "-f", "{connection_file}"], 14 | "display_name": "RPyc-Python", 15 | "language": "Python" 16 | } 17 | 18 | 19 | def install_my_kernel_spec(user=True, prefix=None): 20 | if "python2" in sys.executable: 21 | print("I think this needs python3") 22 | with TemporaryDirectory() as td: 23 | os.chmod(td, 0o755) # Starts off as 700, not user readable 24 | with open(os.path.join(td, 'kernel.json'), 'w') as f: 25 | json.dump(kernel_json, f, sort_keys=True, indent=2) 26 | # TODO: Copy resources once they're specified 27 | 28 | print('Installing IPython kernel spec of RPyc') 29 | k = KernelSpecManager() 30 | k.install_kernel_spec(td, 'RPyc', user=user, 31 | replace=True, prefix=prefix) 32 | 33 | h = k.get_kernel_spec("RPyc") 34 | print("...into", h.resource_dir) 35 | 36 | 37 | def _is_root(): 38 | try: 39 | return os.geteuid() == 0 40 | except AttributeError: 41 | return False # assume not an admin on non-Unix platforms 42 | 43 | 44 | def main(argv=None): 45 | parser = argparse.ArgumentParser( 46 | description='Install KernelSpec for RPyc Kernel' 47 | ) 48 | prefix_locations = parser.add_mutually_exclusive_group() 49 | 50 | prefix_locations.add_argument( 51 | '--user', 52 | help='Install KernelSpec in user homedirectory', 53 | action='store_true' 54 | ) 55 | prefix_locations.add_argument( 56 | '--sys-prefix', 57 | help='Install KernelSpec in sys.prefix. Useful in conda / virtualenv', 58 | action='store_true', 59 | dest='sys_prefix' 60 | ) 61 | prefix_locations.add_argument( 62 | '--prefix', 63 | help='Install KernelSpec in this prefix', 64 | default=None 65 | ) 66 | 67 | args = parser.parse_args(argv) 68 | 69 | user = False 70 | prefix = None 71 | if args.sys_prefix: 72 | prefix = sys.prefix 73 | elif args.prefix: 74 | prefix = args.prefix 75 | elif args.user or not _is_root(): 76 | user = True 77 | 78 | install_my_kernel_spec(user=user, prefix=prefix) 79 | 80 | 81 | if __name__ == '__main__': 82 | main() 83 | -------------------------------------------------------------------------------- /tests/usage_connect.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "source": [ 7 | "# 请将 \"192.168.0.156\" 修改成设备的 IP 地址,即可连接你的硬件。\r\n", 8 | "$connect(\"192.168.0.156\")\r\n", 9 | "# 推荐使用 adb forward tcp:18812 tcp:18812 && adb forward tcp:18812 tcp:18812 配置到本机自动连接。\r\n", 10 | "\r\n", 11 | "# 连接成功会显示远端机器的硬件信息。\r\n", 12 | "import platform\r\n", 13 | "print(platform.uname())" 14 | ], 15 | "outputs": [ 16 | { 17 | "output_type": "stream", 18 | "name": "stdout", 19 | "text": [ 20 | "uname_result(system='Linux', node='sipeed', release='4.9.118', version='#110 PREEMPT Fri Dec 25 08:16:04 UTC 2020', machine='armv7l', processor='')\n" 21 | ] 22 | } 23 | ], 24 | "metadata": {} 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 1, 29 | "source": [ 30 | "import platform" 31 | ], 32 | "outputs": [], 33 | "metadata": {} 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 1, 38 | "source": [ 39 | "print(platform.uname())" 40 | ], 41 | "outputs": [ 42 | { 43 | "output_type": "stream", 44 | "name": "stdout", 45 | "text": [ 46 | "uname_result(system='Linux', node='sipeed', release='4.9.118', version='#110 PREEMPT Fri Dec 25 08:16:04 UTC 2020', machine='armv7l', processor='')\n" 47 | ] 48 | } 49 | ], 50 | "metadata": {} 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": 1, 55 | "source": [ 56 | "$connect(\"192.168.0.156\")\r\n", 57 | "# 当它重新连接以后环境是干净的,所以会报 name 'platform' is not defined 。\r\n", 58 | "print(platform.uname())" 59 | ], 60 | "outputs": [ 61 | { 62 | "output_type": "stream", 63 | "name": "stderr", 64 | "text": [ 65 | "2021-01-21 14:27:38,508 - rpyc_ikernel - ERROR - name 'platform' is not defined\n", 66 | "\n", 67 | "========= Remote Traceback (1) =========\n", 68 | "Traceback (most recent call last):\n", 69 | " File \"/usr/lib/python3.8/site-packages/rpyc/core/protocol.py\", line 324, in _dispatch_request\n", 70 | " res = self._HANDLERS[handler](self, *args)\n", 71 | " File \"/usr/lib/python3.8/site-packages/rpyc/core/protocol.py\", line 592, in _handle_call\n", 72 | " return obj(*args, **dict(kwargs))\n", 73 | " File \"/usr/lib/python3.8/site-packages/rpyc/core/service.py\", line 152, in execute\n", 74 | " execute(text, self.namespace)\n", 75 | " File \"\", line 3, in \n", 76 | "NameError: name 'platform' is not defined\n", 77 | "\n" 78 | ] 79 | } 80 | ], 81 | "metadata": {} 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": 1, 86 | "source": [ 87 | "import platform" 88 | ], 89 | "outputs": [], 90 | "metadata": {} 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": 1, 95 | "source": [ 96 | "print(platform.uname())" 97 | ], 98 | "outputs": [ 99 | { 100 | "output_type": "stream", 101 | "name": "stdout", 102 | "text": [ 103 | "uname_result(system='Linux', node='sipeed', release='4.9.118', version='#110 PREEMPT Fri Dec 25 08:16:04 UTC 2020', machine='armv7l', processor='')\n" 104 | ] 105 | } 106 | ], 107 | "metadata": {} 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": null, 112 | "source": [], 113 | "outputs": [], 114 | "metadata": {} 115 | } 116 | ], 117 | "metadata": { 118 | "celltoolbar": "编辑元数据", 119 | "kernelspec": { 120 | "display_name": "RPyc-Python", 121 | "language": "Python", 122 | "name": "rpyc" 123 | }, 124 | "language_info": { 125 | "codemirror_mode": "python", 126 | "file_extension": ".py", 127 | "mimetype": "text/python", 128 | "name": "Python" 129 | } 130 | }, 131 | "nbformat": 4, 132 | "nbformat_minor": 4 133 | } -------------------------------------------------------------------------------- /tests/test_cli.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | test_cli.py 5 | 6 | Check that the output of rpyc_ikernel is as expected when run as a 7 | commandline script. 8 | 9 | """ 10 | 11 | import sys 12 | 13 | import pytest 14 | 15 | # pytest tries to include this if Test is kept in the name 16 | from scripttest import TestFileEnvironment as Env 17 | 18 | # Generic just for testing 19 | env = Env() 20 | 21 | 22 | def test_launcher_doing_nothing(): 23 | """Run without arguments.""" 24 | # This says error because there are no arguments -- that's fine. 25 | result = env.run("rpyc_ikernel", expect_error=True) 26 | assert result.returncode != 0 27 | # Help is useless, really 28 | result = env.run("rpyc_ikernel", "--help") 29 | assert "usage: rpyc_ikernel" in result.stdout 30 | 31 | 32 | def test_launcher_version(): 33 | """Launcher not manager.""" 34 | # version goes to stderr in PY2, stdout in PY3 35 | result = env.run("rpyc_ikernel", "-V", expect_stderr=True) 36 | assert "Remote Jupyter kernel launcher" in result.stdout + result.stderr 37 | 38 | 39 | def test_manage_doing_nothing(): 40 | """Manage is different to the kernel itself.""" 41 | # Does not error. Gives help. 42 | result = env.run("rpyc_ikernel", "manage") 43 | assert "usage" in result.stdout 44 | result = env.run("rpyc_ikernel", "manage", "--help") 45 | assert "usage" in result.stdout 46 | 47 | 48 | def test_manage_basics(): 49 | result = env.run("rpyc_ikernel", "manage", "-V", expect_stderr=True) 50 | assert "Remote Jupyter kernel manager" in result.stdout + result.stderr 51 | # Show will not error, even with no kernels 52 | result = env.run("rpyc_ikernel", "manage", "--show") 53 | 54 | 55 | def test_minimum_args(): 56 | # interface, kernel_cmd and name are required! 57 | result = env.run( 58 | "rpyc_ikernel", 59 | "manage", 60 | "--add", 61 | "--kernel_cmd=command", 62 | "--name=name", 63 | expect_error=True, 64 | ) 65 | assert "interface must be specified" in result.stderr 66 | result = env.run( 67 | "rpyc_ikernel", 68 | "manage", 69 | "--add", 70 | "--interface=local", 71 | "--name=name", 72 | expect_error=True, 73 | ) 74 | assert "kernel_cmd is required" in result.stderr 75 | result = env.run( 76 | "rpyc_ikernel", 77 | "manage", 78 | "--add", 79 | "--interface=local", 80 | "--kernel_cmd=command", 81 | expect_error=True, 82 | ) 83 | assert "name is required for kernel" in result.stderr 84 | result = env.run( 85 | "rpyc_ikernel", 86 | "manage", 87 | "--add", 88 | "--interface=local", 89 | "--kernel_cmd=command", 90 | "--name=name", 91 | expect_error=False, 92 | ) 93 | assert "Added kernel" in result.stdout 94 | result = env.run("rpyc_ikernel", "manage", "--delete", "rpyc_local_name") 95 | assert "Removed kernel" in result.stdout 96 | 97 | 98 | @pytest.mark.skipif( 99 | sys.platform == "win32", reason="Unicode not working in windows terminal." 100 | ) 101 | def test_unicode(): 102 | created = [] 103 | # Create a unicode containing kernel with unicode in every field possible 104 | result = env.run( 105 | "rpyc_ikernel", 106 | "manage", 107 | "--add", 108 | "--interface=local", 109 | "--kernel_cmd=command ☂", 110 | "--name=name ☂", 111 | "--language=lang ☂", 112 | "--pe=pe ☂", 113 | "--host=host ☂", 114 | "--workdir=dir ☂", 115 | "--remote-precmd=pre ☂", 116 | "--launch-cmd=launch ☂", 117 | "--remote-launch-args=rla ☂", 118 | "--tunnel-hosts=th ☂", 119 | ) 120 | assert u"\u2602" in result.stdout 121 | result = env.run("rpyc_ikernel", "manage", "--show") 122 | assert u"\u2602" in result.stdout 123 | result = env.run( 124 | "rpyc_ikernel", "manage", "--delete", "rpyc_local_name_launch_pe___via_th__" 125 | ) 126 | assert "Removed kernel" in result.stdout 127 | -------------------------------------------------------------------------------- /tests/test_command_fix.py: -------------------------------------------------------------------------------- 1 | """ 2 | test_command_fix.py 3 | 4 | Check the special cases where the kernel command needs fixing up. 5 | 6 | """ 7 | 8 | from rpyc_ikernel.manage import command_fix 9 | 10 | 11 | def test_irkernel_unescaped(capsys): 12 | """Check that it gets escaped.""" 13 | kernel_command = ( 14 | "/usr/lib64/R/bin/R --slave -e IRkernel::main() --args {connection_file}" 15 | ) 16 | fixed_kernel_command = ( 17 | "/usr/lib64/R/bin/R --slave -e 'IRkernel::main()' --args {connection_file}" 18 | ) 19 | assert command_fix(kernel_command) != kernel_command 20 | out, _err = capsys.readouterr() 21 | assert "Escaping" in out 22 | assert command_fix(kernel_command) == fixed_kernel_command 23 | 24 | 25 | def test_irkernel_already_escaped(): 26 | """Don't escape if it should already work.""" 27 | # single quotes 28 | kernel_command = ( 29 | "/usr/lib64/R/bin/R --slave -e 'IRkernel::main()' --args {connection_file}" 30 | ) 31 | assert command_fix(kernel_command) == kernel_command 32 | # double quotes 33 | kernel_command = ( 34 | '/usr/lib64/R/bin/R --slave -e "IRkernel::main()" --args {connection_file}' 35 | ) 36 | assert command_fix(kernel_command) == kernel_command 37 | # escaped 38 | kernel_command = ( 39 | r"/usr/lib64/R/bin/R --slave -e IRkernel::main\(\) --args {connection_file}" 40 | ) 41 | assert command_fix(kernel_command) == kernel_command 42 | 43 | 44 | def test_unescaped_brackets(capsys): 45 | """Warn if there are brackets """ 46 | # unescaped cases 47 | command_fix("one two three ( four five six") 48 | out, _err = capsys.readouterr() 49 | assert "unescaped brackets" in out 50 | 51 | command_fix("one two three ) four five six") 52 | out, _err = capsys.readouterr() 53 | assert "unescaped brackets" in out 54 | 55 | command_fix("one two three (\) four five six") 56 | out, _err = capsys.readouterr() 57 | assert "unescaped brackets" in out 58 | 59 | 60 | def test_escaped_brackets(capsys): 61 | """Don't warn if it seems fine.""" 62 | # escaped cases 63 | command_fix("one two three \(\) four five six") 64 | out, _err = capsys.readouterr() 65 | assert "unescaped brackets" not in out 66 | 67 | command_fix("one two three '()' four five six") 68 | out, _err = capsys.readouterr() 69 | assert "unescaped brackets" not in out 70 | 71 | command_fix('one two three "()" four five six') 72 | out, _err = capsys.readouterr() 73 | assert "unescaped brackets" not in out 74 | 75 | 76 | def test_missing_quotations(capsys): 77 | """Warn if command seems broken.""" 78 | # Broken 79 | command_fix("one two three ' four five six") 80 | out, _err = capsys.readouterr() 81 | assert "missing quotation marks" in out 82 | command_fix('one two three " four five six') 83 | out, _err = capsys.readouterr() 84 | assert "missing quotation marks" in out 85 | # Fine 86 | command_fix("one two three ' four ' five six") 87 | out, _err = capsys.readouterr() 88 | assert "missing quotation marks" not in out 89 | command_fix('one two three " four " five six') 90 | out, _err = capsys.readouterr() 91 | assert "missing quotation marks" not in out 92 | 93 | 94 | # Test when running from the commandline 95 | # pytest tries to include this if Test is kept in the name 96 | from scripttest import TestFileEnvironment as Env 97 | 98 | # Generic just for testing 99 | env = Env() 100 | 101 | 102 | def test_fix_in_manage(): 103 | # interface, kernel_cmd and name are required... 104 | # Break kernel_cmd 105 | result = env.run( 106 | "rpyc_ikernel", 107 | "manage", 108 | "--add", 109 | "--interface=local", 110 | "--kernel_cmd=something IRkernel::main()", 111 | "--name=name", 112 | ) 113 | assert "Escaping IRkernel" in result.stdout 114 | 115 | result = env.run( 116 | "rpyc_ikernel", 117 | "manage", 118 | "--add", 119 | "--interface=local", 120 | "--kernel_cmd=something unescaped()", 121 | "--name=name", 122 | ) 123 | assert "unescaped brackets" in result.stdout 124 | 125 | result = env.run( 126 | "rpyc_ikernel", 127 | "manage", 128 | "--add", 129 | "--interface=local", 130 | "--kernel_cmd=\"something badly quoted'", 131 | "--name=name", 132 | ) 133 | assert "missing quotation marks" in result.stdout 134 | 135 | result = env.run("rpyc_ikernel", "manage", "--delete", "rpyc_local_name") 136 | assert "Removed kernel" in result.stdout 137 | -------------------------------------------------------------------------------- /README_cn.md: -------------------------------------------------------------------------------- 1 | 2 |

3 |

RPyC の IPykernel

4 |

5 | 6 | [![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) 7 | [![PyPI version](https://badge.fury.io/py/rpyc-ikernel.svg)](https://badge.fury.io/py/rpyc-ikernel) 8 | 9 | [English Readme](README.md) 10 | 11 | ## 内核安装方式 12 | 13 | - pip install rpyc_ikernel 14 | - python -m rpyc_ikernel.install 15 | 16 | ![index.png](./tests/images/index.png) 17 | 18 | ## 内核简介 19 | 20 | 本内核继承了IPythonKernel(iPython)类并且支持了低端的硬件(armv7l),能在占用不超过16MB下运行Python编程、实时的图像和视频流 21 | 22 | - 通过[rpyc](https://github.com/tomerfiliba-org/rpyc)实现了远程访问(RPC)内核,详见此[文档](http://rpyc.readthedocs.org/) 23 | 24 | - 为运行[MaixPy3](https://github.com/sipeed/MaixPy3)的远端机器建立了一个RPC服务,来在本地和远程设备之间传输代码并显示图像和结果 25 | 26 | ### 特殊的函数 27 | 28 | | 命令格式 | 命令用途 | 命令用法 | 29 | | ---- | ---- | ---- | 30 | | $connect("localhost") | 连接到远程的IP (例如: "192.168.44.171:18812") | [usage_connect.ipynb](./tests/usage_connect.ipynb) | 31 | 32 | ## 安装方法 33 | 34 | 按以下顺序介绍 35 | 36 | - 为[远端 Python]配置rpyc服务 37 | - 为[本地 Python]配置jupyter环境 38 | 39 | ## 为[远端 Python]配置rpyc服务. 40 | 41 | 在远端设备上使用**ifconfig** 或 **ipconfig**命令获取它的IP地址,并且确保这个地址能被**ping**到 42 | 43 | ### MaixPy3 系列 44 | 45 | 确保远端设备已经被配置为了**Python3**环境,输入`pip3 install maixpy3`来安装**rpyc**服务,拷贝以下命令并且运行来启动服务 46 | 47 | ```shell 48 | python -c "import maix.mjpg;maix.mjpg.start()" 49 | ``` 50 | 51 | ### 其他环境 52 | 53 | 以下代码可以提供一个远程访问环境 54 | 55 | ```python 56 | try: 57 | from rpyc.utils.server import ThreadedServer 58 | from rpyc.core.service import SlaveService 59 | rpyc_server = ThreadedServer( 60 | SlaveService, hostname=HostName, port=RpycPort, reuse_addr=True) 61 | rpyc_server.start() 62 | except OSError as e: 63 | # logging.debug('[%s] OSError: %s' % (__file__, str(e))) # [Errno 98] Address already in use 64 | exit(0) 65 | ``` 66 | 67 | 现在你的rpyc服务已经配置好了 68 | 69 | ## 为[本地 Python]配置jupyter环境 70 | 71 | 以Python3为例,请确保安装了基本的python3和pip3环境和命令行。只需要运行下面这一行代码。 72 | 73 | ```shell 74 | pip3 install rpyc_ikernel && python3 -m rpyc_ikernel.install 75 | ``` 76 | 77 | 大陆的用户可以使用清华源来加速 78 | 79 | ```shell 80 | pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple rpyc_ikernel && python3 -m rpyc_ikernel.install 81 | ``` 82 | 83 | 装完以上的包之后,可以通过输入`jupyter notebook`来启动服务。启动之后,系统默认的浏览器会自动打开(建议使用Chrome和360浏览器极速版)。请选择rpyc内核并新建一个使用该内核的文件 84 | 85 | ![kernels.png](./tests/images/kernels.png) 86 | 87 | 如果你没有看见它,你可以输入`python3 -m rpyc_ikernel.install`来完成内核的安装,然后就可以看到了。 88 | 89 | ### 对于 Windows 90 | 91 | 输入`python -m rpyc_ikernel.install`来完成内核的安装 92 | 93 | 如果有如下常见的找不到模块的错误,它们常见于py2和py3混装的环境,请检查用的python和pip的版本和环境变量。 94 | 95 | - `/usr/bin/python3: Error while finding module specification for'rpyc-ikernel.install' (ModuleNotFoundError: No module named'rpyc-ikernel')` 96 | - `/usr/bin/python: No module named rpyc-ikernel` 97 | 98 | > 对于有些机器的环境,python3被命名为python;或者存在多个版本的python和pip,那么此时需要正确选择使用的python命令。 99 | 100 | 你可以输入`jupyter kernelspec list`来检查正确安装的jupyter内核。如果这里没有rpyc,那么这个内核就还没被安装。 101 | 102 | ```shell 103 | Available kernels: 104 | bash /home/juwan/.local/share/jupyter/kernels/bash 105 | micropython /home/juwan/.local/share/jupyter/kernels/micropython 106 | python3 /home/juwan/.local/share/jupyter/kernels/python3 107 | rpyc /home/juwan/.local/share/jupyter/kernels/rpyc 108 | ``` 109 | 110 | ## 在Jupyter NoteBook中运行Python 111 | 112 | 在运行代码之前,清配置ip地址以连接,否则,它会默认地连接到"localhost"地址请求服务。 113 | 114 | ```python 115 | $connect("192.168.43.44") 116 | import platform 117 | print(platform.uname()) 118 | ``` 119 | 120 | 然后就可看到如下的返回结果 121 | 122 | ```shell 123 | uname_result(system='Linux', node='linux-lab', release='5.4.0-56-generic', version='#62-Ubuntu SMP Mon Nov 23 19:20:19 UTC 2020', machine= 'x86_64', processor='x86_64') 124 | ``` 125 | 126 | ## 常见问题 127 | 128 | 你可以按以下顺序排除问题: 129 | 130 | ### 环境问题 131 | 132 | 当您发现执行Python代码后没有响应时,可以按照以下步骤进行错误诊断。 133 | 134 | - 检查远程设备的rpyc服务是否存在/运行。 (使用ps -a命令) 135 | - 如果在代码仍在运行时按中断按钮无法停止,请刷新代码页或重新启动内核并再次尝试执行代码。 136 | - 重新启动jupyter服务并重新连接到远程设备以执行代码。 137 | 138 | 如果仍不工作,可能是网络问题,请继续进行故障排除。 139 | 140 | ### 网络问题 141 | 142 | 确保本地设备可以连接到远程设备,并能使用Ping或套接字(socket)进行连接。 143 | 144 | - 确定设备所属的网络,并尝试ping从机的IP地址。 145 | - 确定远程端所属的网络,并尝试ping主机IP地址。 146 | - 确保上层路由器的转发规则不限制服务端口18811、18812、18813。 147 | 148 | ### 其他问题 149 | 150 | 拔下网络电缆或重新启动机器,重置硬件和其他重置操作。 151 | 152 | ## 设计灵感 153 | 154 | 内核设计来自以下Python存储库 155 | 156 | - [maixpy3](https://github.com/sipeed/maixpy3) 157 | - [ipykernel](https://github.com/ipython/ipykernel) 158 | - [rpyc](https://github.com/tomerfiliba-org/rpyc) 159 | 160 | 参考内核如下 161 | 162 | - [bash_kernel](https://github.com/takluyver/bash_kernel) 163 | - [ubit_kernel](https://github.com/takluyver/ubit_kernel) 164 | - [remote_ikernel](https://github.com/tdaff/remote_ikernel) 165 | - [jupyter_micropython_kernel](https://github.com/goatchurchprime/jupyter_micropython_kernel) 166 | -------------------------------------------------------------------------------- /tests/test_ugly-code.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "scrolled": true 8 | }, 9 | "outputs": [ 10 | { 11 | "name": "stdout", 12 | "output_type": "stream", 13 | "text": [ 14 | "[ rpyc-kernel ]( running at Wed Dec 1 16:21:01 2021 )\n", 15 | "uname_result(system='Linux', node='sipeed', release='4.9.118', version='#2502 PREEMPT Wed Dec 1 03:11:00 UTC 2021', machine='armv7l', processor='')\n", 16 | "Thu Jan 1 01:10:14 1970\n" 17 | ] 18 | } 19 | ], 20 | "source": [ 21 | "import platform\n", 22 | "print(platform.uname())\n", 23 | "import time\n", 24 | "print(time.asctime())" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 1, 30 | "metadata": {}, 31 | "outputs": [ 32 | { 33 | "name": "stdout", 34 | "output_type": "stream", 35 | "text": [ 36 | "[ rpyc-kernel ]( running at Wed Dec 1 16:21:05 2021 )\n", 37 | "test\n" 38 | ] 39 | }, 40 | { 41 | "data": { 42 | "text/plain": [ 43 | "_get_exception_class..Derived: HELLO\n", 44 | "\n", 45 | "========= Remote Traceback (1) =========\n", 46 | "Traceback (most recent call last):\n", 47 | " File \"/usr/lib/python3.8/site-packages/rpyc/core/protocol.py\", line 324, in _dispatch_request\n", 48 | " File \"/usr/lib/python3.8/site-packages/rpyc/core/protocol.py\", line 592, in _handle_call\n", 49 | " File \"\", line 2, in \n", 50 | "Exception: HELLO\n", 51 | "\n" 52 | ] 53 | }, 54 | "execution_count": 1, 55 | "metadata": {}, 56 | "output_type": "execute_result" 57 | } 58 | ], 59 | "source": [ 60 | "print('test')\n", 61 | "raise Exception('HELLO')" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": 1, 67 | "metadata": {}, 68 | "outputs": [ 69 | { 70 | "name": "stdout", 71 | "output_type": "stream", 72 | "text": [ 73 | "[ rpyc-kernel ]( running at Wed Dec 1 16:21:07 2021 )\n" 74 | ] 75 | }, 76 | { 77 | "data": { 78 | "text/plain": [ 79 | "_get_exception_class..Derived: division by zero\n", 80 | "\n", 81 | "========= Remote Traceback (1) =========\n", 82 | "Traceback (most recent call last):\n", 83 | " File \"/usr/lib/python3.8/site-packages/rpyc/core/protocol.py\", line 324, in _dispatch_request\n", 84 | " File \"/usr/lib/python3.8/site-packages/rpyc/core/protocol.py\", line 592, in _handle_call\n", 85 | " File \"\", line 1, in \n", 86 | "ZeroDivisionError: division by zero\n", 87 | "\n" 88 | ] 89 | }, 90 | "execution_count": 1, 91 | "metadata": {}, 92 | "output_type": "execute_result" 93 | } 94 | ], 95 | "source": [ 96 | "255 / 0" 97 | ] 98 | }, 99 | { 100 | "cell_type": "code", 101 | "execution_count": 1, 102 | "metadata": {}, 103 | "outputs": [ 104 | { 105 | "name": "stdout", 106 | "output_type": "stream", 107 | "text": [ 108 | "[ rpyc-kernel ]( running at Wed Dec 1 16:24:54 2021 )\n", 109 | "4448.158232867\n", 110 | "4449.184878576\n", 111 | "4450.213568327\n", 112 | "4451.241270619\n", 113 | "4452.269931078\n" 114 | ] 115 | } 116 | ], 117 | "source": [ 118 | "\n", 119 | "import time\n", 120 | "for i in range(5):\n", 121 | " time.sleep(1)\n", 122 | " print(time.time())\n" 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": 1, 128 | "metadata": {}, 129 | "outputs": [ 130 | { 131 | "name": "stdout", 132 | "output_type": "stream", 133 | "text": [ 134 | "[ rpyc-kernel ]( running at Wed Dec 1 16:25:07 2021 )\n" 135 | ] 136 | }, 137 | { 138 | "data": { 139 | "text/plain": [ 140 | "\r\n", 141 | "Traceback (most recent call last):\r\n", 142 | " File \"\", line unknown, in \r\n", 143 | "Remote.KeyboardInterrupt\r\n" 144 | ] 145 | }, 146 | "execution_count": 1, 147 | "metadata": {}, 148 | "output_type": "execute_result" 149 | } 150 | ], 151 | "source": [ 152 | "import time\n", 153 | "while True:\n", 154 | " time.sleep(1)" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": 1, 160 | "metadata": {}, 161 | "outputs": [ 162 | { 163 | "name": "stdout", 164 | "output_type": "stream", 165 | "text": [ 166 | "[ rpyc-kernel ]( running at Wed Dec 1 16:25:14 2021 )\n" 167 | ] 168 | }, 169 | { 170 | "data": { 171 | "text/plain": [ 172 | "\r\n", 173 | "Traceback (most recent call last):\r\n", 174 | " File \"\", line unknown, in \r\n", 175 | "Remote.KeyboardInterrupt\r\n" 176 | ] 177 | }, 178 | "execution_count": 1, 179 | "metadata": {}, 180 | "output_type": "execute_result" 181 | } 182 | ], 183 | "source": [ 184 | "while True:\n", 185 | " pass" 186 | ] 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": null, 191 | "metadata": {}, 192 | "outputs": [], 193 | "source": [] 194 | } 195 | ], 196 | "metadata": { 197 | "kernelspec": { 198 | "display_name": "RPyc-Python", 199 | "language": "Python", 200 | "name": "rpyc" 201 | }, 202 | "language_info": { 203 | "codemirror_mode": "python", 204 | "file_extension": ".py", 205 | "mimetype": "text/python", 206 | "name": "Python" 207 | } 208 | }, 209 | "nbformat": 4, 210 | "nbformat_minor": 4 211 | } 212 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 |

RPyC の IPykernel

4 |

5 | 6 | [![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) 7 | [![PyPI version](https://badge.fury.io/py/rpyc-ikernel.svg)](https://badge.fury.io/py/rpyc-ikernel) 8 | 9 | [中文文档](README_cn.md) 10 | 11 | ## Kernel installation 12 | 13 | - pip install rpyc_ikernel 14 | - python -m rpyc_ikernel.install 15 | 16 | ![index.png](./tests/images/index.png) 17 | 18 | ## Kernel introduction 19 | 20 | Inherit the IPythonKernel (iPython) class, and support low-end hardware (armv7l) for Python programming and real-time image and video streaming with less occupation 16M. 21 | 22 | - Implement remote call (RPC) core through [rpyc](https://github.com/tomerfiliba-org/rpyc), see this [docs](http://rpyc.readthedocs.org/). 23 | 24 | - Set up an RPC service for the remote machine with [MaixPy3](https://github.com/sipeed/MaixPy3) and transfer the local code to the remote (remote) to run & display images & get results. 25 | 26 | ### Special functions 27 | 28 | | Command format | Command function | How to use | 29 | | ---- | ---- | ---- | 30 | | $connect("localhost") | Connect to the remote IP address (for example: "192.168.44.171:18812") | [usage_connect.ipynb](./tests/usage_connect.ipynb) | 31 | 32 | ## installation method 33 | 34 | Explain in the following order: 35 | 36 | - Configure rpyc service for [Remote Python]. 37 | - Install jupyter environment for [Local Python]. 38 | 39 | ## Configure rpyc service for [Remote Python]. 40 | 41 | Use **ifconfig** or **ipconfig** on your remote device to get your IP address, please make sure that the address can be **ping**. 42 | 43 | ### MaixPy3 series 44 | 45 | Make sure that the remote device is configured in the **Python3** environment, enter `pip3 install maixpy3` to install the **rpyc** service, copy the following command and run it to start the service. 46 | 47 | ```shell 48 | python -c "import maix.mjpg;maix.mjpg.start()" 49 | ``` 50 | 51 | ### Other environmental 52 | 53 | The following code provides the remote call environment. 54 | 55 | ```python 56 | try: 57 | from rpyc.utils.server import ThreadedServer 58 | from rpyc.core.service import SlaveService 59 | rpyc_server = ThreadedServer( 60 | SlaveService, hostname=HostName, port=RpycPort, reuse_addr=True) 61 | rpyc_server.start() 62 | except OSError as e: 63 | # logging.debug('[%s] OSError: %s' % (__file__, str(e))) # [Errno 98] Address already in use 64 | exit(0) 65 | ``` 66 | 67 | At this time your rpyc service has been up. 68 | 69 | ## Install jupyter environment for [local Python]. 70 | 71 | Take Python3 as an example, please make sure that the basic environment/commands of python3 and pip3 have been installed, just call the code under the command line. 72 | 73 | ```shell 74 | pip3 install rpyc_ikernel && python3 -m rpyc_ikernel.install 75 | ``` 76 | 77 | Tsinghua source can be used for domestic download acceleration. 78 | 79 | ```shell 80 | pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple rpyc_ikernel && python3 -m rpyc_ikernel.install 81 | ``` 82 | 83 | After the above package is installed, enter `jupyter notebook` to start the service. After startup, the system default browser will be opened automatically (recommended foreign Google browser or domestic 360 speed browser), please select the kernel of rpyc and create a new (new) designation The code file of the kernel. 84 | 85 | ![kernels.png](./tests/images/kernels.png) 86 | 87 | If you don't see it, you can enter `python3 -m rpyc_ikernel.install` to complete the kernel installation, and you can see it now. 88 | 89 | ### On Windows 90 | 91 | enter `python -m rpyc_ikernel.install` to complete the kernel installation. 92 | 93 | If there are the following common errors that cannot find modules, they are common in py2 and py3 environments. Please confirm whether the system environment variable is python / pip command. 94 | 95 | - `/usr/bin/python3: Error while finding module specification for'rpyc-ikernel.install' (ModuleNotFoundError: No module named'rpyc-ikernel')` 96 | - `/usr/bin/python: No module named rpyc-ikernel` 97 | 98 | > For some machine environment variables, python3 is python, or multiple versions of python and pip coexist, then the python command is required at this time. 99 | 100 | You can enter `jupyter kernelspec list` to view the currently installed jupyter kernel. If there is no rpyc, the kernel is not installed. 101 | 102 | ```shell 103 | Available kernels: 104 | bash /home/juwan/.local/share/jupyter/kernels/bash 105 | micropython /home/juwan/.local/share/jupyter/kernels/micropython 106 | python3 /home/juwan/.local/share/jupyter/kernels/python3 107 | rpyc /home/juwan/.local/share/jupyter/kernels/rpyc 108 | ``` 109 | 110 | ## Run Python code in Notebook 111 | 112 | Before running the code, please configure the IP address to connect, otherwise it will connect to the "localhost" address to request the service by default. 113 | 114 | ```python 115 | $connect("192.168.43.44") 116 | import platform 117 | print(platform.uname()) 118 | ``` 119 | 120 | It can be seen that the returned results are as follows: 121 | 122 | ```shell 123 | uname_result(system='Linux', node='linux-lab', release='5.4.0-56-generic', version='#62-Ubuntu SMP Mon Nov 23 19:20:19 UTC 2020', machine= 'x86_64', processor='x86_64') 124 | ``` 125 | 126 | ## common problem 127 | 128 | You can troubleshoot problems in the following order: 129 | 130 | ### Environmental issues 131 | 132 | When you find that there is no response after the Python code is executed, you can follow the steps below to troubleshoot the error. 133 | 134 | - Check if the rpyc service of the remote device exists/runs. (Ps -a) 135 | - If pressing the interrupt button fails to stop while the code is still running, please refresh the code page or restart the kernel and try to execute the code again. 136 | - Restart the jupyter service and reconnect to the remote device to execute code. 137 | 138 | If it still does not work, it may be a network problem, so continue to troubleshoot. 139 | 140 | ### Internet problem 141 | 142 | Make sure that the local machine can connect to the remote machine, and use Ping or socket to connect. 143 | 144 | - Determine the network to which the machine belongs, and try to ping the IP address of the slave machine. 145 | - Determine the network to which the remote end belongs, and try to ping the host IP address. 146 | - Make sure that the forwarding rules of the upper router do not restrict the service ports 18811, 18812, 18813. 147 | 148 | ### other problems 149 | 150 | Unplug the network cable or restart the machine, reset the hardware and other reset operations. 151 | 152 | ## Design inspiration 153 | 154 | The kernel design is taken from the following Python repository. 155 | 156 | - [maixpy3](https://github.com/sipeed/maixpy3) 157 | - [ipykernel](https://github.com/ipython/ipykernel) 158 | - [rpyc](https://github.com/tomerfiliba-org/rpyc) 159 | 160 | The reference kernel is as follows. 161 | 162 | - [bash_kernel](https://github.com/takluyver/bash_kernel) 163 | - [ubit_kernel](https://github.com/takluyver/ubit_kernel) 164 | - [remote_ikernel](https://github.com/tdaff/remote_ikernel) 165 | - [jupyter_micropython_kernel](https://github.com/goatchurchprime/jupyter_micropython_kernel) 166 | -------------------------------------------------------------------------------- /tests/test_all.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "scrolled": true 8 | }, 9 | "outputs": [ 10 | { 11 | "name": "stdout", 12 | "output_type": "stream", 13 | "text": [ 14 | "[ rpyc-kernel ]( running at Wed Dec 8 17:39:11 2021 )\n" 15 | ] 16 | } 17 | ], 18 | "source": [ 19 | "while True:\n", 20 | " pass" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "metadata": {}, 27 | "outputs": [ 28 | { 29 | "name": "stdout", 30 | "output_type": "stream", 31 | "text": [ 32 | "[ rpyc-kernel ]( running at Fri Dec 10 05:14:22 2021 )\n" 33 | ] 34 | } 35 | ], 36 | "source": [ 37 | "from maix import camera, image, display\n", 38 | "\n", 39 | "while True:\n", 40 | " tmp = camera.capture()\n", 41 | " ma = tmp.find_blobs([(28,-36,-14,68,-5,15)])\n", 42 | " if ma:\n", 43 | " for i in ma:\n", 44 | " tmp.draw_rectangle(i[\"x\"], i[\"y\"], i[\"x\"] + i[\"w\"], i[\"y\"] + i[\"h\"], (255, 0, 0), 1)\n", 45 | " display.show(tmp)" 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": null, 51 | "metadata": {}, 52 | "outputs": [], 53 | "source": [] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": null, 58 | "metadata": { 59 | "scrolled": false 60 | }, 61 | "outputs": [], 62 | "source": [ 63 | "from maix import display, camera\n", 64 | "while True:\n", 65 | " display.show(camera.capture())" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": null, 71 | "metadata": {}, 72 | "outputs": [], 73 | "source": [ 74 | "\n", 75 | "from PIL import Image\n", 76 | "from PIL import ImageDraw\n", 77 | "from PIL import ImageFont\n", 78 | "\n", 79 | "import time, random\n", 80 | "\n", 81 | "from maix import display\n", 82 | "\n", 83 | "image = Image.new(\"RGB\", (120, 120), (0, 255, 0))# (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))\n", 84 | "draw = ImageDraw.Draw(image)\n", 85 | "\n", 86 | "draw.line((20, 20, 100, 100), 'cyan')\n", 87 | "\n", 88 | "display.show(image)\n" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": null, 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "\n", 98 | "from PIL import Image\n", 99 | "from PIL import ImageDraw\n", 100 | "from PIL import ImageFont\n", 101 | "\n", 102 | "import time, random\n", 103 | "\n", 104 | "from maix import display\n", 105 | "\n", 106 | "image = Image.new(\"RGB\", (120, 120), (255, 0, 0))# (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))\n", 107 | "draw = ImageDraw.Draw(image)\n", 108 | "\n", 109 | "draw.line((20, 20, 100, 100), 'cyan')\n", 110 | "\n", 111 | "display.show(image)\n" 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": null, 117 | "metadata": {}, 118 | "outputs": [], 119 | "source": [ 120 | "\n", 121 | "from PIL import Image\n", 122 | "from PIL import ImageDraw\n", 123 | "from PIL import ImageFont\n", 124 | "\n", 125 | "import time, random\n", 126 | "\n", 127 | "from maix import display\n", 128 | "\n", 129 | "image = Image.new(\"RGB\", (120, 120), (0, 255, 0))# (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))\n", 130 | "draw = ImageDraw.Draw(image)\n", 131 | "\n", 132 | "draw.line((20, 20, 100, 100), 'cyan')\n", 133 | "\n", 134 | "display.show(image)\n" 135 | ] 136 | }, 137 | { 138 | "cell_type": "code", 139 | "execution_count": null, 140 | "metadata": {}, 141 | "outputs": [], 142 | "source": [ 143 | "\n", 144 | "from PIL import Image\n", 145 | "from PIL import ImageDraw\n", 146 | "from PIL import ImageFont\n", 147 | "\n", 148 | "import time, random\n", 149 | "\n", 150 | "from maix import display\n", 151 | "\n", 152 | "image = Image.new(\"RGB\", (120, 120), (255, 0, 0))# (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))\n", 153 | "draw = ImageDraw.Draw(image)\n", 154 | "\n", 155 | "draw.line((20, 20, 100, 100), 'cyan')\n", 156 | "\n", 157 | "display.show(image)\n" 158 | ] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "execution_count": null, 163 | "metadata": {}, 164 | "outputs": [], 165 | "source": [ 166 | "from maix import display, camera\n", 167 | "display.show(camera.capture().resize(100, 100))" 168 | ] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": null, 173 | "metadata": { 174 | "scrolled": true 175 | }, 176 | "outputs": [], 177 | "source": [ 178 | "from maix import display, camera\n", 179 | "while True:\n", 180 | " display.show(camera.capture().resize(100, 100))" 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": null, 186 | "metadata": {}, 187 | "outputs": [], 188 | "source": [ 189 | "from maix import display, camera\n", 190 | "while True:\n", 191 | " display.show(camera.capture().resize(200, 200))" 192 | ] 193 | }, 194 | { 195 | "cell_type": "code", 196 | "execution_count": null, 197 | "metadata": {}, 198 | "outputs": [], 199 | "source": [ 200 | "while True:\n", 201 | " pass" 202 | ] 203 | }, 204 | { 205 | "cell_type": "code", 206 | "execution_count": null, 207 | "metadata": { 208 | "scrolled": false 209 | }, 210 | "outputs": [], 211 | "source": [ 212 | "from maix import display, camera\n", 213 | "while True:\n", 214 | " display.show(camera.capture())" 215 | ] 216 | }, 217 | { 218 | "cell_type": "code", 219 | "execution_count": null, 220 | "metadata": {}, 221 | "outputs": [], 222 | "source": [ 223 | "from maix import display, camera\n", 224 | "camera.config((320, 240))\n", 225 | "while True:\n", 226 | " display.show(camera.capture())" 227 | ] 228 | }, 229 | { 230 | "cell_type": "code", 231 | "execution_count": null, 232 | "metadata": {}, 233 | "outputs": [], 234 | "source": [] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "execution_count": null, 239 | "metadata": {}, 240 | "outputs": [], 241 | "source": [] 242 | }, 243 | { 244 | "cell_type": "code", 245 | "execution_count": null, 246 | "metadata": {}, 247 | "outputs": [], 248 | "source": [] 249 | }, 250 | { 251 | "cell_type": "code", 252 | "execution_count": null, 253 | "metadata": {}, 254 | "outputs": [], 255 | "source": [] 256 | }, 257 | { 258 | "cell_type": "code", 259 | "execution_count": null, 260 | "metadata": {}, 261 | "outputs": [], 262 | "source": [] 263 | }, 264 | { 265 | "cell_type": "code", 266 | "execution_count": null, 267 | "metadata": {}, 268 | "outputs": [], 269 | "source": [] 270 | }, 271 | { 272 | "cell_type": "code", 273 | "execution_count": null, 274 | "metadata": {}, 275 | "outputs": [], 276 | "source": [] 277 | }, 278 | { 279 | "cell_type": "code", 280 | "execution_count": null, 281 | "metadata": {}, 282 | "outputs": [], 283 | "source": [] 284 | }, 285 | { 286 | "cell_type": "code", 287 | "execution_count": null, 288 | "metadata": {}, 289 | "outputs": [], 290 | "source": [] 291 | }, 292 | { 293 | "cell_type": "code", 294 | "execution_count": null, 295 | "metadata": {}, 296 | "outputs": [], 297 | "source": [] 298 | } 299 | ], 300 | "metadata": { 301 | "kernelspec": { 302 | "display_name": "RPyc-Python", 303 | "language": "Python", 304 | "name": "rpyc" 305 | }, 306 | "language_info": { 307 | "codemirror_mode": "python", 308 | "file_extension": ".py", 309 | "mimetype": "text/python", 310 | "name": "Python" 311 | } 312 | }, 313 | "nbformat": 4, 314 | "nbformat_minor": 4 315 | } 316 | -------------------------------------------------------------------------------- /rpyc_ikernel/adb.py: -------------------------------------------------------------------------------- 1 | 2 | # Author: Chema Garcia (aka sch3m4) 3 | # Contact: chema@safetybits.net | http://safetybits.net/contact 4 | # Homepage: http://safetybits.net 5 | # Project Site: http://github.com/sch3m4/pyadb 6 | 7 | import sys 8 | import os 9 | import re 10 | import subprocess 11 | 12 | 13 | class ADB(): 14 | __adb_path = None 15 | __output = None 16 | __error = None 17 | __return = 0 18 | __device = None 19 | # use device with given serial number (overrides $ANDROID_SERIAL) 20 | __target = None 21 | # -s SERIAL 22 | # use device with given serial number (overrides $ANDROID_SERIAL) 23 | devices = [] 24 | try_times = 0 25 | 26 | # reboot modes 27 | REBOOT_RECOVERY = 1 28 | REBOOT_BOOTLOADER = 2 29 | 30 | # default TCP/IP port 31 | DEFAULT_TCP_PORT = 5555 32 | # default TCP/IP host 33 | DEFAULT_TCP_HOST = "localhost" 34 | 35 | def __init__(self, adb_path='adb', device=None): 36 | self.__adb_path = adb_path 37 | if sys.platform.startswith('win'): 38 | exe_path = os.getcwd() + "\\adb\\adb.exe" 39 | # print(self.__adb_path, exe_path) 40 | if os.path.isfile(exe_path): 41 | self.__adb_path = exe_path 42 | 43 | if device: 44 | self.set_target_device(device) 45 | self.connect_check() 46 | return 47 | 48 | if self.devices == None: 49 | self.init_devices() 50 | 51 | if self.devices: 52 | # default use the 1st device [adb -s serial_number] 53 | self.set_target_device(self.devices[0][0]) 54 | self.connect_check() 55 | 56 | def connect_check(self): 57 | ''' 58 | After we initialied an instance of Adb_Wrapper, we should check if it is 59 | working well. If not, we must initial it again. Considering with the 60 | case that we do not need to restart the adb while we re-initial it, so 61 | we set the global flag 'NEED_RESTART_ADB' to False. 62 | ''' 63 | adb_shell_args_test = ['uname'] 64 | ret = self.run_shell_cmd(adb_shell_args_test) 65 | if ret is None or len(ret) == 0: 66 | self.try_times += 1 67 | if self.try_times > 3: 68 | # print("It has tried 3 times, please check your devices.") 69 | return False 70 | # print('[W] Init Android_native_debug falied, try again.') 71 | self.__init__() 72 | return False 73 | return True 74 | 75 | def is_emulator(self): 76 | target_dev = self.get_target_device() 77 | if target_dev.find('emulator') > -1: 78 | return True 79 | 80 | return False 81 | 82 | def __clean__(self): 83 | self.__output = None 84 | self.__error = None 85 | self.__return = 0 86 | 87 | def get_output(self): 88 | return self.__output 89 | 90 | def get_error(self): 91 | return self.__error 92 | 93 | def get_return_code(self): 94 | return self.__return 95 | 96 | def last_failed(self): 97 | ''' 98 | Did the last command fail? 99 | ''' 100 | if self.__output is None and self.__error is not None and self.__return: 101 | return True 102 | return False 103 | 104 | def __build_command__(self, cmd): 105 | ret = None 106 | 107 | if self.__device is not None and self.__target is None: 108 | self.__error = "Must set target device first" 109 | self.__return = 1 110 | return ret 111 | 112 | if sys.platform.startswith('win'): 113 | exe_path = os.getcwd() + "\\adb\\adb.exe" 114 | if os.path.isfile(exe_path): 115 | self.__adb_path = exe_path 116 | ret = self.__adb_path + " " 117 | if self.__target is not None: 118 | ret += "-s " + self.__target + " " 119 | if isinstance(cmd, list): 120 | ret += ' '.join(cmd) 121 | else: 122 | ret += cmd 123 | else: 124 | ret = [self.__adb_path] 125 | if self.__target is not None: 126 | ret += ["-s", self.__target] 127 | for i in cmd: 128 | ret.append(i) 129 | 130 | return ret 131 | 132 | def run_cmd(self, cmd): 133 | ''' 134 | Runs a command by using adb tool ($ adb ) 135 | cmd have to be a list. 136 | ''' 137 | self.__clean__() 138 | 139 | if self.__adb_path is None: 140 | self.__error = "ADB path not set" 141 | self.__return = 1 142 | return 143 | 144 | if not isinstance(cmd, list): 145 | cmd = cmd.split() 146 | 147 | # For compat of windows 148 | cmd_list = self.__build_command__(cmd) 149 | 150 | adb_proc = subprocess.Popen(cmd_list, stdin=subprocess.PIPE, 151 | stdout=subprocess.PIPE, 152 | stderr=subprocess.PIPE, 153 | shell=False) 154 | (self.__output, self.__error) = adb_proc.communicate() 155 | self.__return = adb_proc.returncode 156 | 157 | def run_shell_cmd(self, cmd): 158 | ''' 159 | Executes a shell command 160 | adb shell 161 | ''' 162 | self.__clean__() 163 | if not isinstance(cmd, list): 164 | cmd = cmd.split() 165 | sh_cmd = cmd.copy() 166 | sh_cmd.insert(0, 'shell') 167 | self.run_cmd(sh_cmd) 168 | return self.__output 169 | 170 | def get_version(self): 171 | ''' 172 | Returns ADB tool version 173 | adb version 174 | ''' 175 | self.run_cmd("version") 176 | ret = self.__output.split()[-1:][0] 177 | return ret 178 | 179 | def check_path(self): 180 | ''' 181 | Intuitive way to verify the ADB path 182 | ''' 183 | if self.get_version() is None: 184 | return False 185 | return True 186 | 187 | def set_adb_path(self, adb_path): 188 | ''' 189 | Sets ADB tool absolute path 190 | ''' 191 | if os.path.isfile(adb_path) is False: 192 | return False 193 | self.__adb_path = adb_path 194 | return True 195 | 196 | def get_adb_path(self): 197 | ''' 198 | Returns ADB tool path 199 | ''' 200 | return self.__adb_path 201 | 202 | def start_server(self): 203 | ''' 204 | Starts ADB server 205 | adb start-server 206 | ''' 207 | self.__clean__() 208 | self.run_cmd('start-server') 209 | return self.__output 210 | 211 | def kill_server(self): 212 | ''' 213 | Kills ADB server 214 | adb kill-server 215 | ''' 216 | self.__clean__() 217 | self.run_cmd('kill-server') 218 | 219 | def restart_server(self): 220 | ''' 221 | Restarts ADB server 222 | ''' 223 | self.kill_server() 224 | return self.start_server() 225 | 226 | def restore_file(self, file_name): 227 | ''' 228 | Restore device contents from the backup archive 229 | adb restore 230 | ''' 231 | self.__clean__() 232 | self.run_cmd(['restore', file_name]) 233 | return self.__output 234 | 235 | def wait_for_device(self): 236 | ''' 237 | Blocks until device is online 238 | adb wait-for-device 239 | ''' 240 | self.__clean__() 241 | self.run_cmd('wait-for-device') 242 | return self.__output 243 | 244 | def get_help(self): 245 | ''' 246 | Returns ADB help 247 | adb help 248 | ''' 249 | self.__clean__() 250 | self.run_cmd('help') 251 | return self.__output 252 | 253 | def init_devices(self): 254 | ''' 255 | Returns a list of connected devices 256 | adb devices 257 | ''' 258 | self.run_cmd(['devices', '-l']) 259 | lines = re.split(r'\n', self.__output.decode(encoding='utf-8')) 260 | # print('init_devices', self.__output.decode(encoding='utf-8')) 261 | for line in lines[1:]: # [1:-1]: 262 | dev = line.split() 263 | if len(dev): 264 | self.devices.append(dev) 265 | 266 | def set_target_device(self, device): 267 | ''' 268 | Select the device to work with 269 | ''' 270 | self.__clean__() 271 | self.__target = device 272 | return True 273 | 274 | def get_target_device(self): 275 | ''' 276 | Returns the selected device to work with 277 | ''' 278 | return self.__target 279 | 280 | def get_state(self): 281 | ''' 282 | Get ADB state 283 | adb get-state 284 | ''' 285 | self.__clean__() 286 | self.run_cmd('get-state') 287 | return self.__output 288 | 289 | def get_serialno(self): 290 | ''' 291 | Get serialno from target device 292 | adb get-serialno 293 | ''' 294 | self.__clean__() 295 | self.run_cmd('get-serialno') 296 | return self.__output 297 | 298 | def reboot_device(self, mode): 299 | ''' 300 | Reboot the target device 301 | adb reboot recovery/bootloader 302 | ''' 303 | self.__clean__() 304 | if mode not in (self.REBOOT_RECOVERY, self.REBOOT_BOOTLOADER): 305 | self.__error = "mode must be REBOOT_RECOVERY/REBOOT_BOOTLOADER" 306 | self.__return = 1 307 | return self.__output 308 | self.run_cmd(["reboot", "%s" % "recovery" 309 | if mode == self.REBOOT_RECOVERY else "bootloader"]) 310 | return self.__output 311 | 312 | def check_root(self): 313 | self.run_shell_cmd(['whoami']) 314 | return 'root' in self.get_output().decode() 315 | 316 | def set_system_rw(self): 317 | ''' 318 | Mounts /system as rw 319 | adb remount 320 | ''' 321 | self.__clean__() 322 | self.run_cmd("remount") 323 | return self.__output 324 | 325 | def get_remote_file(self, remote, local): 326 | ''' 327 | Pulls a remote file 328 | adb pull remote local 329 | ''' 330 | self.__clean__() 331 | self.run_cmd(['pull', remote, local]) 332 | 333 | if self.__error is not None and "bytes in" in self.__error: 334 | self.__output = self.__error 335 | self.__error = None 336 | 337 | return self.__output 338 | 339 | def push_local_file(self, local, remote): 340 | ''' 341 | Push a local file 342 | adb push local remote 343 | ''' 344 | self.__clean__() 345 | self.run_cmd(['push', local, remote]) 346 | return self.__output 347 | 348 | def listen_usb(self): 349 | ''' 350 | Restarts the adbd daemon listening on USB 351 | adb usb 352 | ''' 353 | self.__clean__() 354 | self.run_cmd("usb") 355 | return self.__output 356 | 357 | def listen_tcp(self, port=DEFAULT_TCP_PORT): 358 | ''' 359 | Restarts the adbd daemon listening on the specified port 360 | adb tcpip 361 | ''' 362 | self.__clean__() 363 | self.run_cmd(['tcpip', port]) 364 | return self.__output 365 | 366 | def get_bugreport(self): 367 | ''' 368 | Return all information from the device that should be included in a bug report 369 | adb bugreport 370 | ''' 371 | self.__clean__() 372 | self.run_cmd("bugreport") 373 | return self.__output 374 | 375 | def get_jdwp(self): 376 | ''' 377 | List PIDs of processes hosting a JDWP transport 378 | adb jdwp 379 | ''' 380 | self.__clean__() 381 | self.run_cmd("jdwp") 382 | return self.__output 383 | 384 | def get_logcat(self, lcfilter=""): 385 | ''' 386 | View device log 387 | adb logcat 388 | ''' 389 | self.__clean__() 390 | self.run_cmd(['logcat', lcfilter]) 391 | return self.__output 392 | 393 | def run_emulator(self, cmd=""): 394 | ''' 395 | Run emulator console command 396 | ''' 397 | self.__clean__() 398 | self.run_cmd(['emu', cmd]) 399 | return self.__output 400 | 401 | def connect_remote(self, host=DEFAULT_TCP_HOST, port=DEFAULT_TCP_PORT): 402 | ''' 403 | Connect to a device via TCP/IP 404 | adb connect host:port 405 | ''' 406 | self.__clean__() 407 | self.run_cmd(['connect', "%s:%s" % (host, port)]) 408 | return self.__output 409 | 410 | def disconnect_remote(self, host=DEFAULT_TCP_HOST, port=DEFAULT_TCP_PORT): 411 | ''' 412 | Disconnect from a TCP/IP device 413 | adb disconnect host:port 414 | ''' 415 | self.__clean__() 416 | self.run_cmd(['disconnect', "%s:%s" % (host, port)]) 417 | return self.__output 418 | 419 | def ppp_over_usb(self, tty=None, params=""): 420 | ''' 421 | Run PPP over USB 422 | adb ppp 423 | ''' 424 | self.__clean__() 425 | if tty is None: 426 | return self.__output 427 | 428 | cmd = ["ppp", tty] 429 | if params != "": 430 | cmd += params 431 | 432 | self.run_cmd(cmd) 433 | return self.__output 434 | 435 | def sync_directory(self, directory=""): 436 | ''' 437 | Copy host->device only if changed (-l means list but don't copy) 438 | adb sync 439 | ''' 440 | self.__clean__() 441 | self.run_cmd(['sync', directory]) 442 | return self.__output 443 | 444 | def forward_socket(self, local=None, remote=None): 445 | ''' 446 | Forward socket connections 447 | adb forward 448 | ''' 449 | self.__clean__() 450 | if local is None or remote is None: 451 | return self.__output 452 | self.run_cmd(['forward', local, remote]) 453 | return self.__output 454 | 455 | def uninstall(self, package=None, keepdata=False): 456 | ''' 457 | Remove this app package from the device 458 | adb uninstall [-k] package 459 | ''' 460 | self.__clean__() 461 | if package is None: 462 | return self.__output 463 | 464 | cmd = 'uninstall ' 465 | if keepdata: 466 | cmd += '-k ' 467 | cmd += package 468 | self.run_cmd(cmd.split()) 469 | return self.__output 470 | 471 | def install(self, fwdlock=False, reinstall=False, sdcard=False, pkgapp=None): 472 | ''' 473 | Push this package file to the device and install it 474 | adb install [-l] [-r] [-s] 475 | -l -> forward-lock the app 476 | -r -> reinstall the app, keeping its data 477 | -s -> install on sdcard instead of internal storage 478 | ''' 479 | 480 | self.__clean__() 481 | if pkgapp is None: 482 | return self.__output 483 | 484 | cmd = "install " 485 | if fwdlock is True: 486 | cmd += "-l " 487 | if reinstall is True: 488 | cmd += "-r " 489 | if sdcard is True: 490 | cmd += "-s " 491 | 492 | cmd += pkgapp 493 | self.run_cmd(cmd.split()) 494 | return self.__output 495 | 496 | def find_binary(self, name=None): 497 | ''' 498 | Look for a binary file on the device 499 | ''' 500 | self.run_shell_cmd(['which', name]) 501 | 502 | if self.__output is None: # not found 503 | self.__error = "'%s' was not found" % name 504 | elif self.__output.strip() == "which: not found": # which binary not available 505 | self.__output = None 506 | self.__error = "which binary not found" 507 | else: 508 | self.__output = self.__output.strip() 509 | 510 | return self.__output 511 | 512 | adb = ADB() 513 | 514 | def bind_rpycs(): 515 | # return 516 | global adb 517 | # for item in adb.devices: 518 | # print(item) 519 | # if adb.check_root(): 520 | # print("I'm root.") 521 | if(adb.connect_check()): 522 | import time 523 | for i in range(3): 524 | adb.run_shell_cmd("ps | grep 'from maix import mjpg;mjpg.start();' | awk '{print $1}'") 525 | res = adb.get_output() 526 | if res: 527 | tmp = (res.decode()) 528 | tmp = tmp.replace('\r', '') 529 | res = tmp.split('\n') 530 | # filter(None, res) 531 | res = [x for x in res if x != ''] 532 | # print(i, len(res), res) # exist server 533 | if (len(res) > 1): 534 | return True 535 | 536 | # ---- 537 | adb.run_shell_cmd('/etc/init.d/S52ntpd stop') 538 | adb.run_shell_cmd("ps | grep python | awk '{print $1}' | xargs kill -9") 539 | # ---- 540 | adb.forward_socket('tcp:18811', 'tcp:18811') 541 | adb.forward_socket('tcp:18811', 'tcp:18811') 542 | adb.forward_socket('tcp:18811', 'tcp:18811') 543 | adb.forward_socket('tcp:18812', 'tcp:18812') 544 | adb.forward_socket('tcp:18812', 'tcp:18812') 545 | adb.forward_socket('tcp:18812', 'tcp:18812') 546 | adb.forward_socket('tcp:22', 'tcp:22') 547 | adb.forward_socket('tcp:22', 'tcp:22') 548 | adb.forward_socket('tcp:22', 'tcp:22') 549 | adb.run_shell_cmd("python -c 'from maix import mjpg;mjpg.start();'") 550 | # adb.run_shell_cmd('/etc/init.d/S40network stop') 551 | # print(adb.get_output().decode()) 552 | # adb.run_shell_cmd('killall tcpsvd') 553 | # print(adb.get_output().decode()) 554 | # adb.run_shell_cmd("ps | grep python | awk '{print $1}' | xargs kill -9") 555 | # # adb.run_shell_cmd('/etc/init.d/S51dropbear stop') 556 | # # print(adb.get_output().decode()) 557 | # adb.run_shell_cmd('/etc/init.d/S52ntpd stop') 558 | # # print(adb.get_output().decode()) 559 | # adb.forward_socket('tcp:18811', 'tcp:18811') 560 | # adb.forward_socket('tcp:18812', 'tcp:18812') 561 | # adb.run_shell_cmd('python -c "import maix.mjpg;maix.mjpg.start()"') 562 | # # print(adb.get_output().decode()) 563 | # # import sys 564 | # sys.exit(666) 565 | return False 566 | 567 | if __name__ == "__main__": 568 | # adb.run_shell_cmd('/etc/init.d/S52ntpd stop') 569 | # adb.run_shell_cmd("ps | grep python | awk '{print $1}' | xargs kill -9") 570 | # # ---- 571 | adb.forward_socket('tcp:18811', 'tcp:18811') 572 | adb.forward_socket('tcp:18812', 'tcp:18812') 573 | # while True: 574 | # print('bind_rpycs()', bind_rpycs()) 575 | -------------------------------------------------------------------------------- /rpyc_ikernel/kernel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | kernel.py 5 | 6 | Run standard IPython/Jupyter kernels on remote machines using 7 | job schedulers. 8 | 9 | """ 10 | 11 | import imghdr 12 | import base64 13 | import os 14 | import logging 15 | import time 16 | import traceback 17 | import urllib.request 18 | import re 19 | import _thread 20 | import socket 21 | 22 | from PIL import Image # , UnidentifiedImageError 23 | import rpyc 24 | 25 | try: 26 | from pexpect import spawn as pexpect_spawn 27 | except ImportError: 28 | from pexpect.popen_spawn import PopenSpawn 29 | 30 | class pexpect_spawn(PopenSpawn): 31 | def isalive(self): 32 | return self.proc.poll() is None 33 | 34 | # from .scheduler import Scheduler 35 | 36 | from .adb import bind_rpycs, adb 37 | 38 | def config_maixpy3(): 39 | from threading import Thread 40 | import time 41 | def tmp(): 42 | while True: 43 | bind_rpycs() 44 | time.sleep(1) 45 | t = Thread(target=tmp,args=()) 46 | t.setDaemon(True) 47 | t.start() 48 | 49 | # from ipykernel.kernelbase import Kernel 50 | from ipykernel.ipkernel import IPythonKernel 51 | 52 | ######################################################################################################################################## 53 | 54 | class ProtoError(Exception): 55 | pass 56 | 57 | class MjpgSocket(): 58 | 59 | def read_header_line(self, stream): 60 | '''Read one header line within the stream. 61 | The headers come right after the boundary marker and usually contain 62 | headers like Content-Type and Content-Length which determine the type and 63 | length of the data portion. 64 | ''' 65 | return stream.readline().decode('iso8859-1').strip() 66 | 67 | 68 | def read_headers(self, stream, boundary): 69 | '''Read and return stream headers. 70 | Each stream data packet starts with an empty line, followed by a boundary 71 | marker, followed by zero or more headers, followed by an empty line, 72 | followed by actual data. This function reads and parses the entire header 73 | section. It returns a dictionary with all the headers. Header names are 74 | converted to lower case. Each value in the dictionary is a list of header 75 | fields values. 76 | ''' 77 | l = self.read_header_line(stream) 78 | if l == '': 79 | l = self.read_header_line(stream) 80 | # print("read_headers", l, boundary) 81 | if l != boundary: 82 | raise ProtoError('Boundary string expected, but not found') 83 | 84 | headers = {} 85 | while True: 86 | l = self.read_header_line(stream) 87 | # An empty line indicates the end of the header section 88 | if l == '': 89 | break 90 | 91 | # Parse the header into lower case header name and header body 92 | i = l.find(':') 93 | if i == -1: 94 | raise ProtoError('Invalid header line: ' + l) 95 | name = l[:i].lower() 96 | body = l[i+1:].strip() 97 | 98 | lst = headers.get(name, list()) 99 | lst.append(body) 100 | headers[name] = lst 101 | 102 | return headers 103 | 104 | 105 | def skip_data(self, stream, left): 106 | while left: 107 | rv = stream.read(left) 108 | if len(rv) == 0 and left: 109 | raise ProtoError('Not enough data in chunk') 110 | left -= len(rv) 111 | 112 | 113 | def read_data(self, buf, stream, length): 114 | v = memoryview(buf)[:length] 115 | while len(v): 116 | n = stream.readinto(v) 117 | if n == 0 and len(v): 118 | raise ProtoError('Not enough data in chunk') 119 | v = v[n:] 120 | return buf 121 | 122 | 123 | def parse_content_length(self, headers): 124 | # Parse and check Content-Length. The header must be present in 125 | # each chunk, otherwise we wouldn't know how much data to read. 126 | clen = headers.get('content-length', None) 127 | try: 128 | return int(clen[0]) 129 | except (ValueError, TypeError): 130 | raise ProtoError('Invalid or missing Content-Length') 131 | 132 | 133 | def check_content_type(self, headers, type_): 134 | ctype = headers.get('content-type', None) 135 | if ctype is None: 136 | raise ProtoError('Missing Content-Type header') 137 | ctype = ctype[0] 138 | 139 | i = ctype.find(';') 140 | if i != -1: 141 | ctype = ctype[:i] 142 | 143 | if ctype != type_: 144 | raise ProtoError('Wrong Content-Type: %s' % ctype) 145 | 146 | return True 147 | 148 | 149 | def open_mjpeg_stream(self, stream): 150 | if stream.status != 200: 151 | raise ProtoError('Invalid response from server: %d' % stream.status) 152 | h = stream.info() 153 | 154 | boundary = h.get_param('boundary', header='content-type', unquote=True) 155 | if boundary is None: 156 | raise ProtoError('Content-Type header does not provide boundary string') 157 | # boundary = '--' + boundary 158 | 159 | return boundary 160 | 161 | 162 | def read_mjpeg_frame(self, stream, boundary): 163 | hdr = self.read_headers(stream, boundary) 164 | clen = self.parse_content_length(hdr) 165 | if clen == 0: 166 | raise EOFError('End of stream reached') 167 | self.check_content_type(hdr, 'image/jpeg') 168 | buf = bytearray(clen) 169 | self.read_data(buf, stream, clen) 170 | return buf 171 | 172 | def unit_test(self): 173 | try: 174 | url = "http://127.0.0.1:18811" 175 | with urllib.request.urlopen(url, timeout = 3) as stream: 176 | boundary = self.open_mjpeg_stream(stream) 177 | while True: 178 | tmp = self.read_mjpeg_frame(stream, boundary) 179 | print(len(tmp), tmp) 180 | except EOFError: 181 | pass 182 | except Exception as e: 183 | traceback.print_exc() 184 | 185 | def __init__(self, url: str): 186 | self._url = url 187 | self.stream = urllib.request.urlopen(url, timeout = 9) 188 | self.boundary = self.open_mjpeg_stream(self.stream) 189 | 190 | def iter_content(self): 191 | while True: 192 | yield self.read_mjpeg_frame(self.stream, self.boundary) 193 | 194 | 195 | # import requests 196 | # class MjpgReader(): 197 | # """ 198 | # MJPEG format 199 | 200 | # Content-Type: multipart/x-mixed-replace; boundary=--BoundaryString 201 | # --BoundaryString 202 | # Content-type: image/jpg 203 | # Content-Length: 12390 204 | 205 | # ... image-data here ... 206 | 207 | 208 | # --BoundaryString 209 | # Content-type: image/jpg 210 | # Content-Length: 12390 211 | 212 | # ... image-data here ... 213 | # """ 214 | 215 | # def __init__(self, url: str): 216 | # self._url = url 217 | # self.session = requests.Session() 218 | 219 | # def iter_content(self): 220 | # """ 221 | # Raises: 222 | # RuntimeError 223 | # """ 224 | 225 | # r = self.session.get(self._url, stream=True, timeout=3) 226 | # # r = requests.get(self._url, stream=True, timeout=3) 227 | 228 | # # parse boundary 229 | # content_type = r.headers['content-type'] 230 | # index = content_type.rfind("boundary=") 231 | # assert index != 1 232 | # boundary = content_type[index+len("boundary="):] + "\r\n" 233 | # boundary = boundary.encode('utf-8') 234 | 235 | # rd = io.BufferedReader(r.raw) 236 | # while True: 237 | # self._skip_to_boundary(rd, boundary) 238 | # length = self._parse_length(rd) 239 | # yield rd.read(length) 240 | 241 | # def _parse_length(self, rd) -> int: 242 | # length = 0 243 | # while True: 244 | # line = rd.readline() 245 | # if line == b'\r\n': 246 | # return length 247 | # if line.startswith(b"Content-Length"): 248 | # length = int(line.decode('utf-8').split(": ")[1]) 249 | # assert length > 0 250 | 251 | # def _skip_to_boundary(self, rd, boundary: bytes): 252 | # for _ in range(10): 253 | # if boundary in rd.readline(): 254 | # break 255 | # else: 256 | # raise RuntimeError("Boundary not detected:", boundary) 257 | 258 | ######################################################################################################################################## 259 | 260 | def _setup_logging(verbose=logging.INFO): 261 | 262 | log = logging.getLogger("rpyc_ikernel") 263 | log.setLevel(verbose) 264 | 265 | console = logging.StreamHandler() 266 | formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s') 267 | console.setFormatter(formatter) 268 | 269 | log.handlers = [] 270 | log.addHandler(console) 271 | 272 | # So that we can attach these to pexpect for debugging purposes 273 | # we need to make them look like files 274 | def _write(*args, **_): 275 | """ 276 | Method to attach to a logger to allow it to act like a file object. 277 | 278 | """ 279 | message = args[0] 280 | # convert bytes from pexpect to something that prints better 281 | if hasattr(message, "decode"): 282 | message = message.decode("utf-8") 283 | 284 | for line in message.splitlines(): 285 | if line.strip(): 286 | log.debug(line) 287 | 288 | def _pass(): 289 | """pass""" 290 | pass 291 | 292 | log.write = _write 293 | log.flush = _pass 294 | 295 | return log 296 | 297 | # _async_raise(ident, SystemExit) 298 | 299 | 300 | def _async_raise(tid): 301 | import inspect 302 | import ctypes 303 | exctype = KeyboardInterrupt 304 | """raises the exception, performs cleanup if needed""" 305 | tid = ctypes.c_long(tid) 306 | if not inspect.isclass(exctype): 307 | exctype = type(exctype) 308 | res = ctypes.pythonapi.PyThreadState_SetAsyncExc( 309 | tid, ctypes.py_object(exctype)) 310 | if res == 0: 311 | return # maybe thread killed 312 | # raise ValueError("invalid thread id") 313 | if res != 1: 314 | # """if it returns a number greater than one, you're in trouble, 315 | # and you should call it again with exc=NULL to revert the effect""" 316 | ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None) 317 | raise SystemError("PyThreadState_SetAsyncExc failed") 318 | 319 | 320 | class RPycKernel(IPythonKernel): 321 | implementation = 'rpyc_kernel' 322 | 323 | language_info = {'name': 'Python', 324 | 'codemirror_mode': 'python', 325 | 'mimetype': 'text/python', 326 | 'file_extension': '.py'} 327 | 328 | def __init__(self, **kwargs): 329 | IPythonKernel.__init__(self, **kwargs) 330 | self.log = _setup_logging() 331 | self.remote = None 332 | self.address = "localhost" 333 | self.clear_output = True 334 | self.last_result = "" 335 | # for do_handle 336 | self.pattern = re.compile("[$](.*?)[(](.*)[)]") 337 | self.commands = { 338 | 'exec': '%s', 339 | 'connect': 'self.connect_remote(%s)', 340 | } 341 | config_maixpy3() 342 | # self.do_reconnect() 343 | # bind_rpycs() 344 | 345 | 346 | def do_reconnect(self): 347 | global adb 348 | import sys 349 | for i in range(5): 350 | try: 351 | self.remote = rpyc.classic.connect(self.address) 352 | self.remote.modules.sys.stdin = sys.stdin 353 | self.remote.modules.sys.stdout = sys.stdout 354 | self.remote.modules.sys.stderr = sys.stderr 355 | self.remote._config['sync_request_timeout'] = None 356 | self.remote_exec = rpyc.async_(self.remote.modules.builtins.exec) # Independent namespace 357 | # self.remote_exec = rpyc.async_(self.remote.execute) # Common namespace 358 | return True 359 | # ConnectionRefusedError: [Errno 111] Connection refused 360 | except Exception as e: 361 | self.remote = None 362 | # self.log.debug('%s on Remote IP: %s' % (repr(e), self.address)) 363 | print("[ rpyc-kernel ]( Connect IP: %s at %s)" % (self.address, time.asctime())) 364 | time.sleep(2) 365 | print("[ rpyc-kernel ]( Connect IP: %s fail! )" % (self.address)) 366 | if sys.platform.startswith('win'): 367 | try: 368 | if adb.connect_check(): 369 | adb.kill_server() 370 | except Exception as e: 371 | self.log.info("[ rpyc-kernel ]( adb %s )" % (str(e))) 372 | # import sys 373 | # sys.exit() 374 | 375 | def check_connect(self): 376 | if self.remote: 377 | try: 378 | if self.remote.closed: 379 | raise Exception('remote %s closed' % self.address) 380 | self.log.debug('checking... (%s)' % self.remote.closed) 381 | self.remote.ping() # fail raise PingError 382 | return True 383 | except Exception as e: # PingError 384 | # self.log.error(repr(e)) 385 | if self.remote != None: 386 | self.remote.close() 387 | self.remote = None 388 | return self.do_reconnect() 389 | 390 | def connect_remote(self, address="localhost"): 391 | self.address = address 392 | self.do_reconnect() 393 | 394 | def _ready_display(self, port=18811): 395 | self._media_client, self._media_port, self._media_work = None, port, True 396 | self._clear_display(self.remote) 397 | self._media_work = True 398 | _thread.start_new_thread(self._update_display, ()) 399 | 400 | def _clear_display(self, remote): 401 | self.log.debug('[%s] _clear_display %s %s' % (self.remote, self._media_work, self._media_client)) 402 | try: 403 | # if self._media_work == True or self._media_client != None: 404 | # self.log.info('[%s] _media_work %s _media_client %s' % (self.remote, self._media_work, self._media_client)) 405 | # time.sleep(1) # many while True maybe ouput last result 406 | self._media_work = False 407 | remote.modules['maix.mjpg'].clear_mjpg() 408 | except Exception as e: 409 | self.log.debug(e) 410 | 411 | def _update_display(self): 412 | # return 413 | while self._media_work: 414 | try: 415 | self.log.debug('[%s] _update_display_ ' % (self._media_client)) 416 | if self._media_client == None: 417 | try: 418 | self._media_client = MjpgSocket("http://%s:%d" % (self.address, self._media_port)) 419 | self.log.debug('[%s] connect... (%s)' % (self._media_client, os.getpid())) 420 | except socket.timeout as e: 421 | self.log.debug(e) 422 | break 423 | if self._media_client != None: 424 | # for content in self._media_client.iter_content(): 425 | # self.log.info('iter_content... (%s)' % len(content)) 426 | try: 427 | content = next(self._media_client.iter_content()) 428 | image_type = imghdr.what(None, content) 429 | # import io 430 | # self.log.info(image_type) 431 | # img = io.BytesIO(content) 432 | # Image.open(img) 433 | # tmp = Image.open() 434 | # buf = io.BytesIO() 435 | # tmp.resize((tmp.size[0], tmp.size[1])).save(buf, format = "JPEG") 436 | # buf.flush() 437 | # content = buf.getvalue() 438 | if self.clear_output: # used when updating lines printed 439 | self.send_response(self.iopub_socket, 440 | 'clear_output', {"wait": True}) 441 | # self.log.info(content) 442 | # image_type = imghdr.what(None, content) 443 | # self.log.info(image_type) 444 | image_data = base64.b64encode(content).decode('iso8859-1') 445 | # self.log.info(len(image_data)) 446 | self.send_response(self.iopub_socket, 'display_data', { 447 | 'data': { 448 | 'image/' + image_type: image_data 449 | }, 450 | 'metadata': {} 451 | }) 452 | except Exception as e: 453 | self.log.debug('[%s] Exception %s' % (self._media_client, e)) 454 | # except UnicodeDecodeError as e: 455 | # self.log.debug('[%s] UnicodeDecodeError %s ' % (self._media_client, e)) 456 | except Image.UnidentifiedImageError as e: 457 | self.log.debug('[%s] UnidentifiedImageError %s' % (self._media_client, e)) 458 | # time.sleep(0.02) 459 | time.sleep(0.01) 460 | # except OSError as e: 461 | # pass 462 | # except ConnectionResetError as e: 463 | except Exception as e: 464 | self.log.debug(e) 465 | # import traceback 466 | # traceback.print_exc() 467 | if (self._media_client): 468 | try: 469 | self._media_client.stream.close() 470 | except Exception as e: 471 | self.log.debug('[%s] Exception ' % (e)) 472 | self._media_client = None 473 | 474 | def kill_task(self): 475 | try: 476 | master = rpyc.classic.connect(self.address) 477 | thread = master.modules.threading 478 | # print(thread.enumerate()) # kill remote's thread 479 | lists = [i for i in thread.enumerate() if i.__class__.__name__ not in [ 480 | 'MjpgServerThread', '_MainThread']] 481 | kills = [i.ident for i in lists if i.ident not in [ 482 | thread.main_thread().ident, thread.get_ident()]] 483 | # print(kills) 484 | for id in kills: 485 | try: 486 | master.teleport(_async_raise)(id) 487 | except Exception as e: 488 | self.log.debug('teleport Exception (%s)' % repr(e)) 489 | # print(master.modules.threading.enumerate()) 490 | # master.modules['traceback'].print_exc() 491 | self._clear_display(master) 492 | master.close() 493 | except Exception as e: 494 | self.log.debug(e) 495 | 496 | def do_handle(self, code): 497 | # self.log.debug(code) 498 | # code = re.sub(r'([#](.*)[\n])', '', code) # clear '# etc...' but bug have "#" 499 | # self.log.info(code) 500 | 501 | cmds = self.pattern.findall(code) 502 | # self.log.info(cmds) 503 | for cmd in cmds: 504 | _format = self.commands.get(cmd[0], None) 505 | if _format: 506 | # print(_format % cmd[1]) 507 | exec(_format % cmd[1]) 508 | code = self.pattern.sub('', code) 509 | 510 | # self.log.debug(code) 511 | return code 512 | 513 | def do_execute(self, code, silent, store_history=True, user_expressions=None, allow_stdin=False): 514 | if not code.strip(): 515 | return {'status': 'ok', 'execution_count': self.execution_count, 516 | 'payload': [], 'user_expressions': {}} 517 | self.log.debug(code) 518 | 519 | # Handle the host call code 520 | code = self.do_handle(code) 521 | 522 | interrupted = False 523 | self.last_result = "" 524 | if self.check_connect(): 525 | try: 526 | try: 527 | print("[ rpyc-kernel ]( running at %s )" % (time.asctime())) 528 | self._ready_display() 529 | 530 | # self.remote.modules.builtins.exec(code) 531 | 532 | self.result = self.remote_exec(code, self.remote.modules.builtins.globals()) 533 | # self.result.wait() 534 | def get_result(result): 535 | if result.error: 536 | pass # is error 537 | self.log.debug(result.value) 538 | # print('get_result', result, result.value, result.error) 539 | self.result.add_callback(get_result) 540 | # self.log.info('self.result.ready (%s)' % repr(self.result.ready)) 541 | while self.result.ready == False: 542 | # self.log.info('self.result.ready (%s)' % repr(self.result.ready)) 543 | time.sleep(0.001) # print(end='') 544 | # time.sleep(0.2) 545 | # with rpyc.classic.redirected_stdio(self.remote): 546 | # self.remote_exec(code) 547 | 548 | # self.remote.execute(code) 549 | # self.log.info(self.result) 550 | except KeyboardInterrupt as e: 551 | # self.remote.execute("raise KeyboardInterrupt") # maybe raise main_thread Exception 552 | interrupted = True 553 | # self.kill_task() 554 | self.last_result = '\r\nTraceback (most recent call last):\r\n File "", line unknown, in \r\nRemote.KeyboardInterrupt\r\n' 555 | self.log.debug(self.last_result) 556 | # raise e 557 | # remote stream has been closed(cant return info) 558 | except EOFError as e: 559 | self.log.debug(e) 560 | # self.remote.close() # not close self 561 | try: 562 | self.remote.modules.os._exit(233) # should close remote 563 | except Exception as e: 564 | pass 565 | except Exception as e: 566 | import traceback, sys 567 | # traceback.print_exc() 568 | exc_type, exc_value, exc_traceback = sys.exc_info() 569 | for s in traceback.format_exception(exc_type, exc_value, exc_traceback): 570 | if "Remote Traceback" in s: 571 | self.last_result = "" 572 | self.last_result += s 573 | # self.log.error(e) 574 | # raise e 575 | pass 576 | finally: 577 | self.kill_task() 578 | 579 | if len(self.last_result) > 0: 580 | self.send_response(self.iopub_socket, 'execute_result', { 581 | 'execution_count': self.execution_count, 582 | 'status': 'ok', 583 | 'payload': [], 584 | 'user_expressions': {}, 585 | 'data': { 586 | # 'text/' + image_type: image_data 587 | "text/plain" : str(self.last_result) 588 | }, 589 | 'metadata': {}, 590 | }) 591 | 592 | if interrupted: 593 | return {'status': 'abort', 'execution_count': self.execution_count} 594 | 595 | return { 596 | 'status': 'ok', 597 | 'execution_count': self.execution_count, 598 | 'payload': [], 599 | 'user_expressions': {} 600 | } 601 | -------------------------------------------------------------------------------- /tests/usage_display.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "[ rpyc-kernel ]( running at Thu Oct 28 16:46:43 2021 )\n", 13 | "uname_result(system='Linux', node='sipeed', release='4.9.118', version='#2369 PREEMPT Tue Oct 26 08:46:44 UTC 2021', machine='armv7l', processor='')\n" 14 | ] 15 | } 16 | ], 17 | "source": [ 18 | "import platform\n", 19 | "print(platform.uname())" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 1, 25 | "metadata": {}, 26 | "outputs": [ 27 | { 28 | "data": { 29 | "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAHgAeADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwCxRRRX52flIUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUwHfT79Bz/F96gHaQf4qsALdJheJBWsIe19QKtFLjBwaMVlJcujASiiikAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUoJByDikpRTUrO6As8XQ7CYf8Aj9V8Hp2oHXjpVji6HYTD/wAfrq0rr+9+YtyrRSkEHBFJXK1bRjCiiikAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUoJByDSUU07O6AtcXQ7CYf8Aj9ViDnB6UAnORwKs8XQ9Jh/4/XVpXX978wKtLSkYODTa5GmtGG4UUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFOBwcikpKE2tUG5a4uh6TD/x+q2DnB6UAnPHSrPF0OwmH/j9deldf3vzAq0UpBBwRSVytWdmAUUUUgCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAClBIORSUU07aoC1xdDsJh/4/VYg5welAJzkcCrPF0PSYf+P11aV1/e/MCrS0pGDg02uRprRhuFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABTgcHIpKShNrVBuWuLoekw/8fqtg5welAJzx0qzxdDsJh/4/XXpXX978wKtFKQQcEUlcrVnZgFFFFIAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigApQSDkUlFNO2qAtcXQ7CYf+P1WIOcHpQCc5HAqzxdD0mH/AI/XVpXX978wKtLSkYODTa5GmtGG4UUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFOBwcikpKE2tUG5a4uh6TD/wAfqtg5welAJzx0qzxdDsJh/wCP116V1/e/MCrRSkEHBFJXK1Z2YBRRRSAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKUEg5FJRTTtqgLXF0OwmH/j9ViDnB6UAnORwKs8XQ9Jh/4/XVpXX978wKtLSkYODTa5GmtGG4UUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFOBwcikpKE2tUG5a4uh6TD/AMfqtg5welAJzx0qzxdDsJh/4/XXpXX978wKtFKQQcEUlcrVnZgFFFFIAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigApQSDkUlFNO2qAtcXQ7CYf8Aj9ViDnB6UAnORwKs8XQ9Jh/4/XVpXX978wKtLSkYODTa5GmtGG4UUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFOBwcikpKE2tUG5a4uh6TD/x+q2DnB6UAnPHSrPF0OwmH/j9deldf3vzAq0UpBBwRSVytWdmAUUUUgCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAClBIORSUU07aoC1xdDsJh/4/VYg5welAJzkcCrPF0PSYf+P11aV1/e/MCrS0pGDg02uRprRhuFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABTgcHIpKShNrVBuWuLoekw/8fqtg5welAJzx0qzxdDsJh/4/XXpXX978wKtFKQQcEUlcrVnZgFFFFIAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigApQSDkUlFNO2qAtcXQ7CYf+P1WIOcHpQCc5HAqzxdD0mH/AI/XVpXX978wKtLSkYODTa5GmtGG4UUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFOBwcikpKE2tUG5a4uh6TD/wAfqtg5welAJzx0qzxdDsJh/wCP116V1/e/MCrRSkEHBFJXK1Z2YBRRRSAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKUEg5FJRTTtqgLXF0OwmH/j9ViDnB6UAnORwKs8XQ9Jh/4/XVpXX978wKtLSkYODTa5GmtGG4UUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFOBwcikpKE2tUG5a4uh6TD/AMfqtg5welAJzx0qzxdDsJh/4/XXpXX978wKtFKQQcEUlcrVnZgFFFFIAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigApQSDkUlFNO2qAtcXQ7CYf8Aj9ViDnB6UAnORwKs8XQ9Jh/4/XVpXX978wKtLSkYODTa5GmtGG4UUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFOBwcikpKE2tUG5a4uh6TD/x+q2DnB6UAnPHSrPF0OwmH/j9deldf3vzAq0UpBBwRSVytWdmAUUUUgCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAClBIORSUU07aoC1xdDsJh/4/VYg5welAJzkcCrPF0PSYf+P11aV1/e/MCrS0pGDg02uRprRhuFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABTgcHIpKShNrVBuWuLoekw/8fqtg5welAJzx0qzxdDsJh/4/XXpXX978wKtFKQQcEUlcrTWjAKKKKQBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABSgkHINJRTTs7oC1xdDsJh/wCP1XPXnpR3z2qfi6HpMP8Ax+upyVVf3vzDcrUUpHY8GkrkBBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUoPcdaSijULD5JHlfJpDzSZO2lxtGTWkpc/qA2iiiswCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/2Q==" 30 | }, 31 | "metadata": {}, 32 | "output_type": "display_data" 33 | }, 34 | { 35 | "name": "stdout", 36 | "output_type": "stream", 37 | "text": [ 38 | "[ rpyc-kernel ]( running at Wed Dec 1 16:27:52 2021 )\n" 39 | ] 40 | } 41 | ], 42 | "source": [ 43 | "\n", 44 | "from PIL import Image\n", 45 | "from PIL import ImageDraw\n", 46 | "from PIL import ImageFont\n", 47 | "\n", 48 | "import time, random\n", 49 | "\n", 50 | "from maix import display\n", 51 | "\n", 52 | "image = Image.new(\"RGB\", (240, 240), (0, 231, 0))# (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))\n", 53 | "draw = ImageDraw.Draw(image)\n", 54 | "\n", 55 | "draw.line((20, 20, 200, 200), 'cyan')\n", 56 | "\n", 57 | "display.show(image)\n" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": 1, 63 | "metadata": { 64 | "scrolled": false 65 | }, 66 | "outputs": [ 67 | { 68 | "data": { 69 | "image/jpeg": "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" 70 | }, 71 | "metadata": {}, 72 | "output_type": "display_data" 73 | }, 74 | { 75 | "data": { 76 | "text/plain": [ 77 | "\r\n", 78 | "Traceback (most recent call last):\r\n", 79 | " File \"\", line unknown, in \r\n", 80 | "Remote.KeyboardInterrupt\r\n" 81 | ] 82 | }, 83 | "execution_count": 1, 84 | "metadata": {}, 85 | "output_type": "execute_result" 86 | } 87 | ], 88 | "source": [ 89 | "from maix import display, camera\n", 90 | "from PIL import ImageDraw\n", 91 | "import time\n", 92 | "while True:\n", 93 | " im = camera.capture()\n", 94 | " display.show(im)" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": 1, 100 | "metadata": {}, 101 | "outputs": [ 102 | { 103 | "data": { 104 | "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAHgAeADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDhKKKK+cP2YKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAHZCgEjkdfelbcjBGUrIf4TSEgleNxXkCtjK+IkGcJq4HPYTgfoMCtoQVReZ5+KxU8PNJfD18jHApDQQUYqwIYHBBo61m731O+MoyimhKKKKkYUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQA4CkBKMGUkMDkEUUdad3e4nGMo2aNn5dfTsmqIPoLgf0wKx2BVirKQwOCD2oDFWDKSGByCO1bPy6+n8Kaog+guB/TArdtVV/e/M8eUpYOVlrTf4GJRSsCrFWBDA4INJWDVj14yUldBRRRSKCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAClDFWDKSGByCKSimnbYUoqSsza+TX4/4U1NB9BcD+mBWMQUYqwIYHBB7UAlGDKSGByCO1bPya/H/CmpqPoLgf0wK6NKq/vfmeSubAy7wf4GLRSkFWKsCGBwQaSuc9aE1JXQUUUUhhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABSqSrBlJDA5BFJRTFOCkrM2vk1+PsmpqPoLgf0wKxiCjFWBDA4IPagEowZSQwOQR2rZ+TX4/wCFNTQfQXA/pgV0aVV/e/M8luWBl3g/wMWilKlWKsCGBwQaSudqx60ZKSugooopDCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAClDFWDKSGByCKSimnbYUoqSsza+TX4/4U1NB9BcD+mBWMQUYqwIYHBB7UAlGDKSGByCO1bPya/H/CmpqPoLgf0wK6NKq/vfmeSubAy7wf4GLRSkFWKsCGBwQaSuc9aE1JXQUUUUhhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABSqSrBlJDA5BFJRTFOCkrM2vk1+PsmpqPoLgf0wKxiCjFWBDA4IPagEowZSQwOQR2rZ+TX4/wCFNTQfQXA/pgV0aVV/e/M8luWBl3g/wMWilKlWKsCGBwQaSudqx60ZKSugooopDCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAClDFWDKSGByCKSimnbYUoqSsza+TX4/4U1NB9BcD+mBWMQUYqwIYHBB7UAlGDKSGByCO1bPya/H/CmpqPoLgf0wK6NKq/vfmeSubAy7wf4GLRSkFWKsCGBwQaSuc9aE1JXQUUUUhhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABSqSrBlJDA5BFJRTFOCkrM2vk1+PsmpqPoLgf0wKxiCjFWBDA4IPagEowZSQwOQR2rZ+TX4/wCFNTQfQXA/pgV0aVV/e/M8luWBl3g/wMWilKlWKsCGBwQaSudqx60ZKSugooopDCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAClDFWDKSGByCKSimnbYUoqSsza+TX4/4U1NB9BcD+mBWMQUYqwIYHBB7UAlGDKSGByCO1bPya/H/CmpqPoLgf0wK6NKq/vfmeSubAy7wf4GLRSkFWKsCGBwQaSuc9aE1JXQUUUUhhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABSqSrBlJDA5BFJRTFOCkrM2vk1+PsmpqPoLgf0wKxiCjFWBDA4IPagEowZSQwOQR2rZ+TX4/wCFNTQfQXA/pgV0aVV/e/M8luWBl3g/wMWilKlWKsCGBwQaSudqx60ZKSugooopDCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAClDFWDKSGByCKSimnbYUoqSsza+TX4/4U1NB9BcD+mBWMQUYqwIYHBB7UAlGDKSGByCO1bPya/H/CmpqPoLgf0wK6NKq/vfmeSubAy7wf4GLRSkFWKsCGBwQaSuc9aE1JXQUUUUhhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABSqSrBlJDA5BFJRTFOCkrM2vk1+PsmpqPoLgf0wKxiCjFWBDA4IPagEowZSQwOQR2rZ+TX4/wCFNTQfQXA/pgV0aVV/e/M8luWBl3g/wMWilKlWKsCGBwQaSudqx60ZKSugooopDCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAClDFWDKSGByCKSimnbYUoqSsza+TX4/4U1NB9BcD+mBWMQUYqwIYHBB7UAlGDKSGByCO1bPya/H/CmpqPoLgf0wK6NKq/vfmeSubAy7wf4GLRSkFWKsCGBwQaSuc9aE1JXQUUUUhhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABSqSrBlJDA5BFJRTFOCkrM2vk1+PsmpqPoLgf0wKxiCjFWBDA4IPagEowZSQwOQR2rZ+TX4/wCFNTQfQXA/pgV0aVV/e/M8luWBl3g/wMWilKlWKsCGBwQaSudqx60ZKSugooopDCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAClDFWDKSGByCKSimnbYUoqSsza+TX4/4U1NB9BcD+mBWMQUYqwIYHBB7UAlGDKSGByCO1bPya/H/CmpqPoLgf0wK6NKq/vfmeSubAy7wf4GLRSkFWKsCGBwQaSuc9aE1JXQUUUUhhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABSqSrBlJDA5BFJRTFOCkrM2vk1+PsmpqPoLgf0wKxiCjFWBDA4IPagEowZSQwOQR2rZ+TX4/wCFNTQfQXA/pgV0aVV/e/M8luWBl3g/wMWilKlWKsCGBwQaSudqx60ZKSugooopDCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAClDFWDKSGByCKSimnbYUoqSsza+TX4/4U1NB9BcD+mBWMQUYqwIYHBB7UAlGDKSGByCO1bPya/H/CmpqPoLgf0wK6NKq/vfmeSubAy7wf4GLRSkFWKsCGBwQaSuc9aE1JXQUUUUhhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABSqSrBlJDA5BFJRTFOCkrM2vk1+PsmpqPoLgf0wKxiCjFWBDA4IPagEowZSQwOQR2rZ+TX4/wCFNTQfQXA/pgV0aVV/e/M8luWBl3g/wMWilKlWKsCGBwQaSudqx60ZKSugooopDCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAClDFWDKSGByCKSimnbYUoqSsza+TX4/4U1NB9BcD+mBWMQUYqwIYHBB7UAlGDKSGByCO1bPya/H/CmpqPoLgf0wK6NKq/vfmeSubAy7wf4GLRSkFWKsCGBwQaSuc9aE1JXQUUUUhhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABSqSrBlJDA5BFJRTFOCkrM2vk1+PsmpqPoLgf0wKxiCjFWBDA4IPagEowZSQwOQR2rZ+TX4/wCFNTQfQXA/pgV0aVV/e/M8luWBl3g/wMWilKlWKsCGBwQaSudqx60ZKSugooopDCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAClDFWDKSGByCKSimnbYUoqSsza+TX4/4U1NB9BcD+mBWMQUYqwIYHBB7UAlGDKSGByCO1bPya/H/CmpqPoLgf0wK6NKq/vfmeSubAy7wf4GLRSkFWKsCGBwQaSuc9aE1JXQUUUUhhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABSqSrBlJDA5BFJRTFOCkrM2vk1+PsmpqPoLgf0wKxiCjFWBDA4IPagEowZSQwOQR2rZ+TX4/wCFNUQfQXA/pgV0aVV/e/M8luWBl3g/wMWilKlWKsCGBwQaSudpo9aMlJXQUUUUhhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABSqSrBlJDA5BFJRTTsxSipKzNv5deT+FNUQfQXA/pgVjNlWKsCGBwQe1G4qwdSQw5BHatj5Nfj/AIU1NR9BcD+mBW0pKaPI9/AT5Y603+BjZpDSkMrFWBBBwQaTFZNWPXhPnVwoooqRhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFACmgEqwZSQQcgikzQKpOwpwU9yxc3M15cme5IzjAAGKhH3GNIflwc5pcEn5+FobuSkoLliNoooqSwooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9k=" 105 | }, 106 | "metadata": {}, 107 | "output_type": "display_data" 108 | }, 109 | { 110 | "name": "stdout", 111 | "output_type": "stream", 112 | "text": [ 113 | "[ rpyc-kernel ]( running at Wed Dec 1 16:27:34 2021 )\n" 114 | ] 115 | } 116 | ], 117 | "source": [ 118 | "\n", 119 | "from PIL import Image\n", 120 | "from PIL import ImageDraw\n", 121 | "from PIL import ImageFont\n", 122 | "\n", 123 | "import time, random\n", 124 | "\n", 125 | "from maix import display\n", 126 | "\n", 127 | "image = Image.new(\"RGB\", (240, 240), (231, 0, 0))# (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))\n", 128 | "draw = ImageDraw.Draw(image)\n", 129 | "\n", 130 | "draw.line((20, 20, 200, 200), 'cyan')\n", 131 | "\n", 132 | "display.show(image)\n" 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": null, 138 | "metadata": {}, 139 | "outputs": [], 140 | "source": [] 141 | } 142 | ], 143 | "metadata": { 144 | "kernelspec": { 145 | "display_name": "RPyc-Python", 146 | "language": "Python", 147 | "name": "rpyc" 148 | }, 149 | "language_info": { 150 | "codemirror_mode": "python", 151 | "file_extension": ".py", 152 | "mimetype": "text/python", 153 | "name": "Python" 154 | } 155 | }, 156 | "nbformat": 4, 157 | "nbformat_minor": 4 158 | } 159 | --------------------------------------------------------------------------------