├── mip_pkg_mpy_22 ├── shell │ ├── vt100.mpy │ ├── wget.mpy │ ├── __init__.mpy │ ├── editor.mpy │ ├── editstr.mpy │ ├── terminal.mpy │ ├── new_urequests │ │ └── __init__.mpy │ └── octopus_shell_help.txt ├── README.md └── package.json ├── vt100.py ├── octopus_shell_help.txt ├── README.md ├── terminal.py ├── wget.py ├── new_urequests └── __init__.py ├── editstr.py ├── editor.py └── __init__.py /mip_pkg_mpy_22/shell/vt100.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octopusengine/micropython-shell/HEAD/mip_pkg_mpy_22/shell/vt100.mpy -------------------------------------------------------------------------------- /mip_pkg_mpy_22/shell/wget.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octopusengine/micropython-shell/HEAD/mip_pkg_mpy_22/shell/wget.mpy -------------------------------------------------------------------------------- /mip_pkg_mpy_22/shell/__init__.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octopusengine/micropython-shell/HEAD/mip_pkg_mpy_22/shell/__init__.mpy -------------------------------------------------------------------------------- /mip_pkg_mpy_22/shell/editor.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octopusengine/micropython-shell/HEAD/mip_pkg_mpy_22/shell/editor.mpy -------------------------------------------------------------------------------- /mip_pkg_mpy_22/shell/editstr.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octopusengine/micropython-shell/HEAD/mip_pkg_mpy_22/shell/editstr.mpy -------------------------------------------------------------------------------- /mip_pkg_mpy_22/shell/terminal.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octopusengine/micropython-shell/HEAD/mip_pkg_mpy_22/shell/terminal.mpy -------------------------------------------------------------------------------- /mip_pkg_mpy_22/shell/new_urequests/__init__.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octopusengine/micropython-shell/HEAD/mip_pkg_mpy_22/shell/new_urequests/__init__.mpy -------------------------------------------------------------------------------- /mip_pkg_mpy_22/README.md: -------------------------------------------------------------------------------- 1 | # micropython-shell 2 | 3 | 4 | ## mip - install 5 | 6 | MicroPython v1.19.1-803-g1583c1f67 on 2023-01-16; ESP32 module with ESP32 + 7 | 8 | 9 | ``` 10 | import mip 11 | mip.install("github:octopusengine/micropython-shell/mip_pkg_mpy_22", target=".") 12 | ``` 13 | -------------------------------------------------------------------------------- /vt100.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | def get_cursor_position(): 4 | height=0 5 | width=0 6 | num='' 7 | 8 | sys.stdout.write('\x1b[6n') 9 | while True: 10 | char = sys.stdin.read(1) 11 | if char is 'R': 12 | width = int(num) 13 | break 14 | if char is '\x1b': 15 | width=0 16 | height=0 17 | num='' 18 | continue 19 | if char is '[': 20 | continue 21 | if char is ';': 22 | height=int(num) 23 | num='' 24 | continue 25 | num+=char 26 | return (width, height) 27 | 28 | def get_terminal_size(): 29 | # Save cursor position 30 | sys.stdout.write('\x1b[s') 31 | 32 | #Move cursor outside of screen 33 | sys.stdout.write('\x1b[999B\x1b[999C') 34 | (w, h) = get_cursor_position() 35 | 36 | # Restore cursor position 37 | sys.stdout.write('\x1b[u') 38 | 39 | return (w, h) 40 | -------------------------------------------------------------------------------- /mip_pkg_mpy_22/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": [ 3 | ["shell/__init__.mpy", "github:octopusengine/micropython-shell/mip_pkg_mpy_22/shell/__init__.mpy"], 4 | ["shell/vt100.mpy", "github:octopusengine/micropython-shell/mip_pkg_mpy_22/shell/vt100.mpy"], 5 | ["shell/terminal.mpy", "github:octopusengine/micropython-shell/mip_pkg_mpy_22/shell/terminal.mpy"], 6 | ["shell/wget.mpy", "github:octopusengine/micropython-shell/mip_pkg_mpy_22/shell/wget.mpy"], 7 | ["shell/editor.mpy", "github:octopusengine/micropython-shell/mip_pkg_mpy_22/shell/editor.mpy"], 8 | ["shell/editstr.mpy", "github:octopusengine/micropython-shell/mip_pkg_mpy_22/shell/editstr.mpy"], 9 | ["shell/octopus_shell_help.txt", "github:octopusengine/micropython-shell/mip_pkg_mpy_22/shell/octopus_shell_help.txt"], 10 | 11 | ["shell/new_urequests/__init__.mpy", "github:octopusengine/micropython-shell/mip_pkg_mpy_22/shell/new_urequests/__init__.mpy"], 12 | 13 | ], 14 | "deps": [ 15 | ], 16 | "version": "0.2.0" 17 | } 18 | -------------------------------------------------------------------------------- /octopus_shell_help.txt: -------------------------------------------------------------------------------- 1 | ---------+--------------------------+-------------------- 2 | | F: file name | 3 | | M: "main.py" | octopusLAB 2019-20 4 | ---------+--------------------------+-------------------- 5 | clear | clear display | 6 | free | free RAM (memory) | 7 | df | Disk Free (flash) | 8 | cd | Change Directory | cd examples / cd .. 9 | pwd | Print Working Dir. | 10 | ls | LiSt files and dir. | ls examples 11 | mkdir | make directory | mkdir newdir 12 | cp | CoPy F (defaul M) | cp test.py back.py 13 | rm | ReMove F | rm test.py 14 | top | main proces info | 15 | wifi | wireless "fidelity" | wifi on / wifi scan 16 | ping | Packet InterNet Groper | ping google.com 17 | ifconfig | wifi InterFace conf. | 18 | wget | wget URL subdir download | wget https://my.web/f.py 19 | find | find "text" | find oled 20 | cat | concatenate F (defaul M) | cat back.py 21 | edit| edit (defaul M) | simple "line editor" 22 | ./ | run F | ./examples/blink.py 23 | | run F & > process/thread | run examples/blink.py & 24 | exit | back to uPy | > Micropython 25 | ---------+--------------------------+--------------------- -------------------------------------------------------------------------------- /mip_pkg_mpy_22/shell/octopus_shell_help.txt: -------------------------------------------------------------------------------- 1 | ---------+--------------------------+-------------------- 2 | | F: file name | 3 | | M: "main.py" | octopusLAB 2019-20 4 | ---------+--------------------------+-------------------- 5 | clear | clear display | 6 | free | free RAM (memory) | 7 | df | Disk Free (flash) | 8 | cd | Change Directory | cd examples / cd .. 9 | pwd | Print Working Dir. | 10 | ls | LiSt files and dir. | ls examples 11 | mkdir | make directory | mkdir newdir 12 | cp | CoPy F (defaul M) | cp test.py back.py 13 | rm | ReMove F | rm test.py 14 | top | main proces info | 15 | wifi | wireless "fidelity" | wifi on / wifi scan 16 | ping | Packet InterNet Groper | ping google.com 17 | ifconfig | wifi InterFace conf. | 18 | wget | wget URL subdir download | wget https://my.web/f.py 19 | find | find "text" | find oled 20 | cat | concatenate F (defaul M) | cat back.py 21 | edit| edit (defaul M) | simple "line editor" 22 | ./ | run F | ./examples/blink.py 23 | | run F & > process/thread | run examples/blink.py & 24 | exit | back to uPy | > Micropython 25 | ---------+--------------------------+--------------------- -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # micropython-shell 2 | simple Linux terminal writen in (and for) ESP32-Micropython 3 |
 4 | >>> shell()
 5 | uPyShell:~/$
 6 | 
7 | ----- 8 |
 9 |   ---------+--------------------------+--------------------
10 |            | F: file name             |
11 |            | M: "main.py"             | octopusLAB 2019-20
12 |   ---------+--------------------------+--------------------
13 |      clear | clear display            |   
14 |       free | free RAM (memory)        |
15 |         df | Disk Free (flash)        |
16 |         cd | Change Directory         | cd examples / cd ..
17 |        pwd | Print Working Dir.       |
18 |         ls | LiSt files and dir.      | ls examples
19 |      mkdir | make directory           | mkdir newdir
20 |         cp | CoPy F (defaul M)        | cp test.py back.py
21 |         rm | ReMove F                 | rm test.py
22 |        top | main proces info         | 
23 |       wifi | wireless "fidelity"      | wifi on / wifi scan
24 |       ping | Packet InterNet Groper   | ping google.com
25 |   ifconfig | wifi InterFace conf.     |
26 |       wget | wget URL subdir download | wget https://my.web/f.py  
27 |       find | find "text"              | find oled 
28 |        cat | concatenate F (defaul M) | cat back.py
29 |        edit| edit (defaul M)          | simple "line editor"
30 |         ./ | run F                    | ./examples/blink.py
31 |            | run F & > process/thread | run examples/blink.py &
32 |       exit | back to uPy              | > Micropython
33 |   ---------+--------------------------+---------------------
34 | 
35 | -------------------------------------------------------------------------------- /terminal.py: -------------------------------------------------------------------------------- 1 | # This file is part of the octopusLAB project 2 | # The MIT License (MIT) 3 | # Copyright (c) 2016-2020 Jan Copak, Petr Kracik, Vasek Chalupnicek 4 | 5 | # from shell.terminal import printTitle 6 | 7 | SEPARATOR_WIDTH = 50 8 | 9 | 10 | def terminal_color(txt,col=33): # default yellow 11 | # 30 black / 31 red / 32 green / 33 yellow / 34 blue / 35 violet / 36 cyan 12 | # 21 underline 13 | # print("\033[32mgreen\033[m") 14 | return "\033[" + str(col) + "m" + str(txt) + "\033[m" 15 | 16 | 17 | def runningEffect(num = 16): 18 | from time import sleep_ms 19 | for ii in range(num): 20 | print(".",end="") 21 | sleep_ms(20) 22 | print() 23 | 24 | 25 | def printBar(num1,num2,char="|",col1=32,col2=33): 26 | print("[",end="") 27 | print((("\033[" + str(col1) + "m" + str(char) + "\033[m")*num1),end="") 28 | print((("\033[" + str(col2) + "m" + str(char) + "\033[m")*num2),end="") 29 | print("] ",end="") 30 | 31 | 32 | # --------------- 33 | 34 | def printHead(s): 35 | print() 36 | print('-' * SEPARATOR_WIDTH) 37 | print("[--- " + s + " ---] ") 38 | 39 | 40 | def printTitle(t,w=SEPARATOR_WIDTH): 41 | print() 42 | print('=' * w) 43 | print("|",end="") 44 | print(t.center(w-2),end="") 45 | print("|") 46 | print('=' * w) 47 | 48 | 49 | def printLog(i,s=""): 50 | print() 51 | print('-' * SEPARATOR_WIDTH) 52 | print("[--- " + str(i) + " ---] " + s) 53 | 54 | 55 | def getUid(short = False): # full or short (5 chars: first 2 + last 3) 56 | import machine, ubinascii 57 | id = ubinascii.hexlify(machine.unique_id()).decode() 58 | if short: 59 | return id[:2]+id[-3:] 60 | else: 61 | return id 62 | 63 | 64 | def printInfo(): 65 | import gc #mem_free 66 | print("> mem_free: "+str(gc.mem_free())) 67 | df() 68 | 69 | 70 | def printMachineInfo(): 71 | import machine 72 | print("> machine.freq: "+str(machine.freq())) 73 | -------------------------------------------------------------------------------- /wget.py: -------------------------------------------------------------------------------- 1 | # wget for octopusLAB MicroPython uPyShell 2 | # copyright (c) 2020 Milan Spacek 3 | # License: MIT 4 | 5 | # wget https://www.octopusengine.org/api/text123.txt [subdir] 6 | # default subdir: "download" 7 | 8 | __version__ = "1.0.0" 9 | 10 | def wget(url="",path="/download"): 11 | from shell.new_urequests import get 12 | from os import mkdir 13 | from gc import collect 14 | debug = False 15 | filename = url.split("/")[-1] 16 | 17 | valid_chars = '-_.()abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' 18 | correc_filename = ''.join(c for c in filename if c in valid_chars) 19 | 20 | if filename != correc_filename: 21 | print("Wrong filename corrected:" + correc_filename) 22 | 23 | if path != "": 24 | try: 25 | if path.endswith('/'): 26 | path = path[:-1] 27 | os.mkdir(path) 28 | except: 29 | pass 30 | path = path + "/" 31 | 32 | collect() 33 | try: 34 | if debug: print("get") 35 | res = get(url, stream = True) 36 | if debug: print("get done") 37 | if debug: print(res.status_code) 38 | collect() 39 | if res.status_code == 200: 40 | print("opening file:", path+correc_filename) 41 | f = open(path+correc_filename, 'w') 42 | if debug: print("opened file") 43 | while True: 44 | if debug: print("reading chunk") 45 | chunk = res.raw.read(256) 46 | if debug: print("writing chunk") 47 | #if debug: print(chunk) 48 | f.write(chunk) 49 | if debug: print("chunk writen") 50 | if not chunk: 51 | break 52 | f.close() 53 | print("Done") 54 | elif res.status_code == 404: 55 | print("File not found") 56 | else: 57 | print("Error status:", res.status_code) 58 | except OSError as e: 59 | if e.args[0] == -202: 60 | print("wget: unable to resolve host address") 61 | else: 62 | print("OSError, exception: {0}".format(e)) 63 | except Exception as e: 64 | print("Error, exception: {0}".format(e)) 65 | -------------------------------------------------------------------------------- /new_urequests/__init__.py: -------------------------------------------------------------------------------- 1 | import usocket 2 | 3 | class Response: 4 | 5 | def __init__(self, f): 6 | self.raw = f 7 | self.encoding = "utf-8" 8 | self._cached = None 9 | 10 | def close(self): 11 | if self.raw: 12 | self.raw.close() 13 | self.raw = None 14 | self._cached = None 15 | 16 | @property 17 | def content(self): 18 | if self._cached is None: 19 | try: 20 | self._cached = self.raw.read() 21 | finally: 22 | self.raw.close() 23 | self.raw = None 24 | return self._cached 25 | 26 | @property 27 | def text(self): 28 | return str(self.content, self.encoding) 29 | 30 | def json(self): 31 | import ujson 32 | return ujson.loads(self.content) 33 | 34 | 35 | def request(method, url, data=None, json=None, headers={}, stream=None, parse_headers=True): 36 | redir_cnt = 1 37 | if json is not None: 38 | assert data is None 39 | import ujson 40 | data = ujson.dumps(json) 41 | 42 | while True: 43 | try: 44 | proto, dummy, host, path = url.split("/", 3) 45 | except ValueError: 46 | proto, dummy, host = url.split("/", 2) 47 | path = "" 48 | if proto == "http:": 49 | port = 80 50 | elif proto == "https:": 51 | import ussl 52 | port = 443 53 | else: 54 | raise ValueError("Unsupported protocol: " + proto) 55 | 56 | if ":" in host: 57 | host, port = host.split(":", 1) 58 | port = int(port) 59 | 60 | ai = usocket.getaddrinfo(host, port, 0, usocket.SOCK_STREAM) 61 | ai = ai[0] 62 | 63 | resp_d = None 64 | if parse_headers is not False: 65 | resp_d = {} 66 | 67 | s = usocket.socket(ai[0], ai[1], ai[2]) 68 | try: 69 | s.connect(ai[-1]) 70 | if proto == "https:": 71 | s = ussl.wrap_socket(s, server_hostname=host) 72 | s.write(b"%s /%s HTTP/1.0\r\n" % (method, path)) 73 | if not "Host" in headers: 74 | s.write(b"Host: %s\r\n" % host) 75 | # Iterate over keys to avoid tuple alloc 76 | for k in headers: 77 | s.write(k) 78 | s.write(b": ") 79 | s.write(headers[k]) 80 | s.write(b"\r\n") 81 | if json is not None: 82 | s.write(b"Content-Type: application/json\r\n") 83 | if data: 84 | s.write(b"Content-Length: %d\r\n" % len(data)) 85 | s.write(b"Connection: close\r\n\r\n") 86 | if data: 87 | s.write(data) 88 | 89 | l = s.readline() 90 | #print(l) 91 | l = l.split(None, 2) 92 | status = int(l[1]) 93 | reason = "" 94 | if len(l) > 2: 95 | reason = l[2].rstrip() 96 | while True: 97 | l = s.readline() 98 | if not l or l == b"\r\n": 99 | break 100 | #print(l) 101 | 102 | if l.startswith(b"Transfer-Encoding:"): 103 | if b"chunked" in l: 104 | raise ValueError("Unsupported " + l.decode()) 105 | elif l.startswith(b"Location:") and 300 <= status <= 399: 106 | if not redir_cnt: 107 | raise ValueError("Too many redirects") 108 | redir_cnt -= 1 109 | url = l[9:].decode().strip() 110 | #print("redir to:", url) 111 | status = 300 112 | break 113 | 114 | if parse_headers is False: 115 | pass 116 | elif parse_headers is True: 117 | l = l.decode() 118 | k, v = l.split(":", 1) 119 | resp_d[k] = v.strip() 120 | else: 121 | parse_headers(l, resp_d) 122 | except OSError: 123 | s.close() 124 | raise 125 | 126 | if status != 300: 127 | break 128 | 129 | resp = Response(s) 130 | resp.status_code = status 131 | resp.reason = reason 132 | if resp_d is not None: 133 | resp.headers = resp_d 134 | return resp 135 | 136 | 137 | def head(url, **kw): 138 | return request("HEAD", url, **kw) 139 | 140 | def get(url, **kw): 141 | return request("GET", url, **kw) 142 | 143 | def post(url, **kw): 144 | return request("POST", url, **kw) 145 | 146 | def put(url, **kw): 147 | return request("PUT", url, **kw) 148 | 149 | def patch(url, **kw): 150 | return request("PATCH", url, **kw) 151 | 152 | def delete(url, **kw): 153 | return request("DELETE", url, **kw) 154 | -------------------------------------------------------------------------------- /editstr.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Petr Viktorin 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining 4 | # a copy of this software and associated documentation files (the "Software"), 5 | # to deal in the Software without restriction, including without limitation 6 | # the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | # and/or sell copies of the Software, and to permit persons to whom the 8 | # Software is furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | # IN THE SOFTWARE. 20 | 21 | # 2020-04-29 Vasek Chalupnicek - fixing TAB and DELETE key 22 | 23 | import sys 24 | 25 | CSI = '\033[' 26 | MOVE_SOL = CSI + 'G' 27 | ERASE_LINE = CSI + '2K' 28 | TAB_SPACES = 4 29 | 30 | def editstr(string): 31 | """Return edited string, or None if cancelled.""" 32 | cursor_pos = len(string) 33 | 34 | def escape(): 35 | c = sys.stdin.read(1) 36 | if c == '[': 37 | esc_bracket() 38 | elif c == 'O': 39 | c = sys.stdin.read(1) 40 | if c == 'H': 41 | home() 42 | if c == 'F': 43 | end() 44 | else: 45 | unknown('ESC O ' + repr(c)) 46 | elif c == 'b': 47 | backward_word() 48 | elif c == 'f': 49 | forward_word() 50 | else: 51 | unknown('ESC ' + repr(c)) 52 | 53 | def esc_bracket(): 54 | c = sys.stdin.read(1) 55 | if ord('0') <= ord(c) <= ord('9'): 56 | c += sys.stdin.read(1) 57 | if c in ('1~', '7~'): 58 | home() 59 | elif c in ('4~', '8~'): 60 | end() 61 | elif c == '3~': 62 | delete() 63 | elif c == '1;': 64 | esc_bracket() 65 | elif c == '5C': 66 | forward_word() 67 | elif c == '5D': 68 | backward_word() 69 | else: 70 | unknown('ESC [ ' + repr(c)) 71 | elif c == 'A': 72 | up_arrow() 73 | elif c == 'B': 74 | down_arrow() 75 | elif c == 'C': 76 | right_arrow() 77 | elif c == 'D': 78 | left_arrow() 79 | elif c == 'H': 80 | home() 81 | elif c == 'F': 82 | end() 83 | else: 84 | unknown('ESC [ ' + repr(c)) 85 | 86 | def redraw(): 87 | print( 88 | MOVE_SOL + ERASE_LINE + string + CSI + str(cursor_pos+1) + 'G', 89 | end='' 90 | ) 91 | 92 | def home(): 93 | nonlocal cursor_pos, string 94 | cursor_pos = 0 95 | 96 | def end(): 97 | nonlocal cursor_pos, string 98 | cursor_pos = len(string) 99 | 100 | def backward_word(): 101 | nonlocal cursor_pos, string 102 | while cursor_pos and string[cursor_pos-1] == ' ': 103 | cursor_pos -= 1 104 | while cursor_pos and string[cursor_pos-1] != ' ': 105 | cursor_pos -= 1 106 | 107 | def forward_word(): 108 | nonlocal cursor_pos, string 109 | while cursor_pos < len(string) and (string+'_')[cursor_pos] == ' ': 110 | cursor_pos += 1 111 | while cursor_pos < len(string) and (string+' ')[cursor_pos] != ' ': 112 | cursor_pos += 1 113 | 114 | def delete(): 115 | nonlocal cursor_pos, string 116 | if cursor_pos < len(string): 117 | string = string[:cursor_pos] + string[cursor_pos+1:] 118 | 119 | def backspace(): 120 | nonlocal cursor_pos, string 121 | if cursor_pos > 0: 122 | cursor_pos -= 1 123 | string = string[:cursor_pos] + string[cursor_pos+1:] 124 | 125 | def right_arrow(): 126 | nonlocal cursor_pos, string 127 | if cursor_pos < len(string): 128 | cursor_pos += 1 129 | 130 | def left_arrow(): 131 | nonlocal cursor_pos, string 132 | if cursor_pos > 0: 133 | cursor_pos -= 1 134 | 135 | def kill_to_end(): 136 | nonlocal cursor_pos, string 137 | string = string[:cursor_pos] 138 | 139 | def kill_to_beginning(): 140 | nonlocal cursor_pos, string 141 | string = string[cursor_pos+1:] 142 | cursor_pos = 0 143 | 144 | def letter(c): 145 | nonlocal cursor_pos, string 146 | if cursor_pos == len(string): 147 | string += c 148 | cursor_pos += 1 149 | else: 150 | string = string[:cursor_pos] + c + string[cursor_pos:] 151 | cursor_pos += 1 152 | 153 | def tab(): 154 | nonlocal cursor_pos, string 155 | if cursor_pos == len(string): 156 | string += ' ' * TAB_SPACES 157 | cursor_pos += TAB_SPACES 158 | else: 159 | string = string[:cursor_pos] + ' ' * TAB_SPACES + string[cursor_pos:] 160 | cursor_pos += TAB_SPACES 161 | 162 | def unknown(s): 163 | print(MOVE_SOL + ERASE_LINE + 'Unknown: ' + s) 164 | 165 | def up_arrow(): 166 | nonlocal cursor_pos 167 | 168 | def down_arrow(): 169 | nonlocal cursor_pos 170 | 171 | try: 172 | while True: 173 | redraw() 174 | c = sys.stdin.read(1) 175 | oc = ord(c) 176 | if oc == 127: 177 | backspace() 178 | elif oc == 1: 179 | # CTRL-A 180 | home() 181 | elif oc == 2: 182 | # CTRL-B 183 | left_arrow() 184 | elif oc == 3: 185 | # CTRL-C 186 | print() 187 | return None 188 | elif oc == 4: 189 | # CTRL-D 190 | delete() 191 | elif oc == 5: 192 | # CTRL-E 193 | end() 194 | elif oc == 6: 195 | # CTRL-F 196 | right_arrow() 197 | elif oc == 11: 198 | # CTRL-K 199 | kill_to_end() 200 | elif oc == 14: 201 | # CTRL-N 202 | down_arrow() 203 | elif oc == 16: 204 | # CTRL-P 205 | up_arrow() 206 | elif oc == 21: 207 | # CTRL-U 208 | kill_to_beginning() 209 | elif oc == 23: 210 | # CTRL-W 211 | backward_word() 212 | elif c in ('\r', '\n'): 213 | return string 214 | elif oc == 27: 215 | escape() 216 | elif oc == 8: 217 | delete() 218 | elif oc == 9: 219 | tab() 220 | elif oc >= 32: 221 | letter(c) 222 | else: 223 | unknown(repr(c)) 224 | finally: 225 | print(MOVE_SOL + ERASE_LINE, end='') 226 | -------------------------------------------------------------------------------- /editor.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This is simple interactive text file editor to be used as part of uPyShell 3 | 4 | This module has a dependency on re module and 5 | local .terminal for color support 6 | 7 | from shell.editor import edit 8 | edit('/test.py') 9 | 10 | # This file is part of the octopusLAB project 11 | # The MIT License (MIT) 12 | # Copyright (c) 2016-2020 Jan Copak, Vasek Chalupnicek 13 | ''' 14 | 15 | __version__ = "1.0.0" 16 | 17 | def edit(filename='/main.py'): 18 | import re 19 | from .editstr import editstr 20 | from .terminal import terminal_color 21 | 22 | show_line_numbers = True 23 | changed = False 24 | file_exists = True 25 | buff = [] # empty new file contains one line with a newline character 26 | 27 | def print_buff(): 28 | ''' 29 | Print current state of the file in editor memory 30 | ''' 31 | nonlocal show_line_numbers, changed, file_exists, buff 32 | 33 | EDITOR_WIDTH = 50 34 | LINE_NO_DIGITS = 4 35 | EDITOR_LINE_PREFIX_TPL = '{:>' + str(LINE_NO_DIGITS) + 'd}│' 36 | EDITOR_TITLE_TOP_PREFIX = ' ' * LINE_NO_DIGITS + '┌' 37 | EDITOR_TITLE_TOP_SUFIX = '┐' 38 | EDITOR_TITLE_PREFIX = ' ' * LINE_NO_DIGITS + '│' 39 | EDITOR_TITLE_SPACER = ' ' 40 | EDITOR_TITLE_SUFIX = '│' 41 | EDITOR_TOP_PREFIX = ' ' * LINE_NO_DIGITS + '├' 42 | EDITOR_TOP_TITLE_SUFIX = '┴' 43 | EDITOR_BOTTOM_PREFIX = ' ' * LINE_NO_DIGITS + '└' 44 | 45 | # find the longest line in the buffer 46 | max_len = 0 47 | for line in buff: 48 | l = len(line) 49 | if l > max_len: 50 | max_len = l 51 | if max_len > EDITOR_WIDTH: 52 | EDITOR_WIDTH = max_len 53 | if show_line_numbers: 54 | EDITOR_WIDTH += len(EDITOR_TITLE_PREFIX) 55 | 56 | # ┌────────────────────────────┐ 57 | # │ /test.py [NEW] [CHANGED] │ 58 | # ├────────────────────────────┴───────── 59 | 60 | title_top_prefix = '' 61 | if show_line_numbers: 62 | title_top_prefix = EDITOR_TITLE_TOP_PREFIX 63 | 64 | editor_title = '{:s}{:s}{:s}'.format( 65 | filename, 66 | not file_exists and ' [NEW]' or '', 67 | file_exists and changed and ' [CHANGED]' or '', 68 | ) 69 | 70 | title_border = '─' * (len(editor_title) + 2 * len(EDITOR_TITLE_SPACER)) 71 | 72 | title_prefix = '' 73 | if show_line_numbers: 74 | title_prefix = EDITOR_TITLE_PREFIX 75 | 76 | top_prefix = '' 77 | if show_line_numbers: 78 | top_prefix = EDITOR_TOP_PREFIX 79 | 80 | title_border_after = '─' * (EDITOR_WIDTH - len(top_prefix) - len(title_border) - len(EDITOR_TOP_TITLE_SUFIX)) 81 | 82 | print() 83 | print('{:s}{:s}{:s}'.format( 84 | terminal_color(title_top_prefix), 85 | terminal_color(title_border), 86 | terminal_color(EDITOR_TITLE_TOP_SUFIX) 87 | )) 88 | print('{:s}{:s}{:s}{:s}{:s}'.format( 89 | terminal_color(title_prefix), 90 | EDITOR_TITLE_SPACER, 91 | editor_title, 92 | EDITOR_TITLE_SPACER, 93 | terminal_color(EDITOR_TITLE_SUFIX) 94 | )) 95 | print('{:s}{:s}{:s}{:s}'.format( 96 | terminal_color(top_prefix), 97 | terminal_color(title_border), 98 | terminal_color(EDITOR_TOP_TITLE_SUFIX), 99 | terminal_color(title_border_after) 100 | )) 101 | 102 | # 1│ 103 | # 2│ 104 | # ... 105 | line_cnt = 0 106 | line_prefix = '' 107 | for line in buff: 108 | if show_line_numbers: 109 | line_cnt += 1 110 | line_prefix = EDITOR_LINE_PREFIX_TPL.format(line_cnt) 111 | print('{:s}{:s}'.format(terminal_color(line_prefix), line)) 112 | # newline at the end of the file 113 | if show_line_numbers: 114 | line_cnt += 1 115 | line_prefix = EDITOR_LINE_PREFIX_TPL.format(line_cnt) 116 | print('{:s}'.format(terminal_color(line_prefix))) 117 | 118 | # └───────────────────────────────────── 119 | bottom_prefix = '' 120 | if show_line_numbers: 121 | bottom_prefix = EDITOR_BOTTOM_PREFIX 122 | print('{:s}{:s}'.format( 123 | terminal_color(bottom_prefix), 124 | terminal_color('─' * (EDITOR_WIDTH - len(bottom_prefix))) 125 | )) 126 | 127 | def print_help(): 128 | print(' h print this help') 129 | print(' p print file (current state in buffer)') 130 | print(' l toggle line numbers (copy mode)') 131 | print(' q quit') 132 | print(' q! x quit without saving changes') 133 | print(' w write file (save)') 134 | print(' wq write into file and quit') 135 | print() 136 | print(' i [] insert new line at given position [int], containing [str] or empty') 137 | print(' a [] insert new line after given [int], containing [str] or empty') 138 | print(' e [] edit line number [int], replace line with [str] or will be prompted') 139 | print(' d delete line number [int]') 140 | print(' c[-] comment/uncomment line [int] with a #, or multiple lines if a range is provided (does each line separately)') 141 | print() 142 | print('NOTE: New line at the end of every non empty file is enforced.') 143 | print() 144 | print('WARNING: Do not use for editing lines exceeding your terminal width - you may BREAK TOUR FILE!') 145 | print() 146 | 147 | def parse_line_no(input): 148 | nonlocal buff 149 | try: 150 | line_no = int(input) 151 | if 1 <= line_no <= len(buff) + 1: 152 | return line_no 153 | except: 154 | pass 155 | print('Line number must be between 1 and {0}, "h" for help'.format(len(buff) + 1)) 156 | return 0 157 | 158 | def parse_line_no_txt(input): 159 | line_no_el = input.split(' ')[0] 160 | line_no = parse_line_no(line_no_el) 161 | txt = '' 162 | try: 163 | # +1 for a space separator between [int] and [txt] 164 | txt = input[len(line_no_el) + 1:] 165 | except: 166 | pass 167 | return line_no, txt 168 | 169 | try: 170 | # read file into buff 171 | with open(filename, 'r') as fi: 172 | for line in fi: 173 | ln = line 174 | if ln.endswith('\r\n'): 175 | # strip CRLF endings 176 | ln = ln[:-2] 177 | elif ln.endswith('\n'): 178 | # strip LF endings 179 | ln = ln[:-1] 180 | buff.append(ln) 181 | except OSError: 182 | file_exists = False 183 | 184 | # welcome messages 185 | print('Welcome to MicroPython text file line editor. Use "h" for help.') 186 | print_buff() 187 | print() 188 | 189 | while True: 190 | action = input(terminal_color('action: ')) 191 | 192 | # action print current buffer 193 | if action == 'p': 194 | print_buff() 195 | 196 | # action print help 197 | elif action == 'h': 198 | print_help() 199 | 200 | # action print help 201 | elif action == 'l': 202 | show_line_numbers = not show_line_numbers 203 | print_buff() 204 | 205 | # action edit one line 206 | elif action.startswith('e'): 207 | line_no, txt = parse_line_no_txt(action[1:]) 208 | if not line_no: 209 | continue 210 | 211 | # get new text interactivelly if was not provided 212 | if not txt: 213 | # old version without editstr 214 | # try: 215 | # print(' ┌───┬───┬───┬───┬───┬───') 216 | # print('Current:', buff[line_no - 1]) 217 | # txt = input(' New: ') 218 | # except IndexError: 219 | # txt = '' 220 | 221 | if line_no > len(buff): 222 | # for empty line on the end of the file use empty string 223 | txt = '' 224 | else: 225 | # for any existing line use current content for editing 226 | txt = buff[line_no - 1] 227 | print(terminal_color('Interactive editing line {}, hit ENTER when done'.format(line_no))) 228 | print(terminal_color('┌───┬───┬───┬───┬───┬───')) 229 | txt = editstr(txt) 230 | print(txt) 231 | 232 | # put new content into buffer 233 | if line_no == len(buff) + 1: 234 | # editing last line means append to buffer 235 | buff.append('{0}'.format(txt)) 236 | else: 237 | # edit regular line 238 | buff[line_no - 1] = '{0}'.format(txt) 239 | changed = True 240 | print_buff() 241 | 242 | # action insert new line 243 | elif action.startswith('i'): 244 | line_no, txt = parse_line_no_txt(action[1:]) 245 | if not line_no: 246 | continue 247 | # insert txt and shift all records in buffer 248 | buff.insert(line_no - 1, '{0}'.format(txt)) 249 | changed = True 250 | print_buff() 251 | 252 | # action append new line 253 | elif action.startswith('a'): 254 | line_no, txt = parse_line_no_txt(action[1:]) 255 | if not line_no: 256 | continue 257 | if line_no == len(buff) + 1: 258 | print('Can not append after the last line in file, use insert instead') 259 | continue 260 | # inserting after index means index is line_no-1 +1 261 | buff.insert(line_no, '{0}'.format(txt)) 262 | changed = True 263 | print_buff() 264 | 265 | # action delete a line 266 | elif action.startswith('d'): 267 | line_no = parse_line_no(action[1:]) 268 | if not line_no: 269 | continue 270 | if line_no == len(buff) + 1: 271 | print('Last line in file can not be removed') 272 | continue 273 | del(buff[line_no - 1]) 274 | changed = True 275 | print_buff() 276 | 277 | # action comment/uncomment a line 278 | elif action.startswith('c'): 279 | idxs = action[1:].split('-') 280 | line_no = parse_line_no(idxs[0]) 281 | if not line_no: 282 | # for invalid inputs skip 283 | continue 284 | line_idx_start = line_no - 1 285 | line_idx_end = line_idx_start 286 | if len(idxs) == 2: 287 | # if we have a range on input 288 | line_no = parse_line_no(idxs[1]) 289 | if not line_no: 290 | # for invalid inputs skip 291 | continue 292 | line_idx_end = line_no - 1 293 | # check start < end 294 | if line_idx_start > line_idx_end: 295 | print('Line range start can not be greater than end') 296 | continue 297 | # for each line in range 298 | for line_idx in range(line_idx_start, line_idx_end + 1): 299 | initial_ws = '' 300 | line_content = '' 301 | try: 302 | mo = re.search(r'^(\s*)(.*)', buff[line_idx]) 303 | # remember starting whitespaces 304 | initial_ws = mo.group(1) 305 | line_content = mo.group(2) 306 | except IndexError: 307 | pass 308 | # if first non-whitespace character is #, then uncomment 309 | if len(line_content) and line_content[0] == '#': 310 | # remove hash and any whitespaces until first character 311 | mo = re.search(r'^(#\s*)(.*)', line_content) 312 | buff[line_idx] = initial_ws + mo.group(2) 313 | # else comment 314 | else: 315 | # add "# " after initial whitespaces and then the rest of the line 316 | buff[line_idx] = initial_ws + '# ' + line_content 317 | changed = True 318 | print_buff() 319 | 320 | # action save buffer to file 321 | elif action in ('w', 'wq'): 322 | print('Writing into file "{0}"'.format(filename)) 323 | file_mode = 'w' 324 | # save buff into file 325 | with open(filename, file_mode) as fo: 326 | for line in buff: 327 | fo.write(line + '\n') 328 | file_exists = True 329 | changed = False 330 | elif action == 'q': 331 | if changed: 332 | print('Changes not saved, force quit with "q!"') 333 | elif action in ('q!', 'x'): 334 | if changed: 335 | print('Changes not saved') 336 | else: 337 | print('Bad option, try again, "h" for help') 338 | 339 | # exit condition 340 | if not changed and action == 'q' or action in ('q!', 'wq', 'x'): 341 | break 342 | 343 | print() 344 | 345 | 346 | if __name__ == '__main__': 347 | # print command line arguments 348 | import sys 349 | 350 | if len(sys.argv) > 1: 351 | edit(sys.argv[1]) 352 | else: 353 | edit() 354 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # This file is part of the octopusLAB project 2 | # The MIT License (MIT) 3 | # Copyright (c) 2016-2020 Jan Copak, Petr Kracik, Vasek Chalupnicek, Jan Cespivo, Milan Spacek 4 | 5 | """ 6 | from shell import shell 7 | shell() 8 | 9 | > cat / edit / ls / mkdir / cp / rm / find / df ... 10 | -------- 11 | autostart: 12 | >>> from config import Config 13 | >>> cc = Config("boot") 14 | >>> cc.set("import_shell",1) 15 | >>> cc.save() 16 | -------- 17 | last update: 18 | """ 19 | 20 | __version__ = "0.33.3-20200630" # 533 21 | 22 | # toto: kill, wget/wsend?, ... 23 | SEPARATOR_WIDTH = 50 24 | 25 | _command_registry = {} 26 | _background_jobs = {} 27 | _wc = None # global wifi_connect 28 | 29 | 30 | def _thread_wrapper(func, job_id, *arguments): 31 | try: 32 | func(*arguments) 33 | finally: 34 | del _background_jobs[job_id] 35 | print('[{job_id}] stopped'.format(job_id=job_id)) 36 | 37 | 38 | def _background_func(func, command_list): 39 | def _wrapper(*arguments): 40 | from _thread import start_new_thread 41 | from time import ticks_ms 42 | job_id = start_time = ticks_ms() 43 | start_new_thread(_thread_wrapper, (func, job_id) + arguments) 44 | _background_jobs[job_id] = {'start_time': start_time, 'command_list': command_list} 45 | print('[{job_id}] started'.format(job_id=job_id)) 46 | 47 | return _wrapper 48 | 49 | 50 | def _register_command(func_name): 51 | def _command(func): 52 | _command_registry[func_name] = func 53 | return func 54 | 55 | return _command 56 | 57 | 58 | def command(func_or_name): 59 | if callable(func_or_name): 60 | return _register_command(func_or_name.__name__)(func_or_name) 61 | elif isinstance(func_or_name, str): 62 | name = func_or_name 63 | return _register_command(name) 64 | 65 | raise ImportError('bad decorator command') 66 | 67 | 68 | 69 | @command 70 | def sleep(seconds): 71 | from time import sleep 72 | sleep(float(seconds)) 73 | 74 | 75 | @command 76 | def ifconfig(): 77 | if _wc is None: 78 | print("ifconfig: Connection not active") 79 | return 80 | 81 | from .terminal import terminal_color 82 | 83 | print('-' * SEPARATOR_WIDTH) 84 | try: 85 | print('IP address:', terminal_color(_wc.sta_if.ifconfig()[0])) 86 | print('subnet mask:', _wc.sta_if.ifconfig()[1]) 87 | print('gateway:', _wc.sta_if.ifconfig()[2]) 88 | print('DNS server:', _wc.sta_if.ifconfig()[3]) 89 | except Exception as e: 90 | print("Exception: {0}".format(e)) 91 | 92 | from ubinascii import hexlify 93 | try: 94 | MAC = terminal_color(hexlify(_wc.sta_if.config('mac'), ':').decode()) 95 | except: 96 | MAC = "Err: w.sta_if" 97 | print("HWaddr (MAC): " + MAC) 98 | print('-' * SEPARATOR_WIDTH) 99 | 100 | 101 | @command 102 | def cat(file='/main.py', title=False): # concatenate - prepare 103 | """print data: f("filename") """ 104 | fi = open(file, 'r') 105 | if title: 106 | from .terminal import printTitle 107 | printTitle("file > " + file) 108 | # file statistic 109 | lines = 0 110 | words = 0 111 | characters = 0 112 | for line in fi: 113 | wordslist = line.split() 114 | lines = lines + 1 115 | words = words + len(wordslist) 116 | characters = characters + len(line) 117 | 118 | print("Statistic > lines: " + str(lines) + " | words: " + str( 119 | words) + " | chars: " + str(characters)) 120 | print('-' * SEPARATOR_WIDTH) 121 | fi = open(file, 'r') 122 | for line in fi: 123 | print(line, end="") 124 | print() 125 | globals()["cat"] = cat 126 | 127 | 128 | @command 129 | def edit(file="/main.py"): 130 | from .editor import edit 131 | edit(file) 132 | 133 | 134 | @command 135 | def ls(directory="", line=False, cols=2, goPrint=True): 136 | from .terminal import terminal_color 137 | debug = False 138 | # if goPrint: printTitle("list > " + directory) 139 | # from os import listdir 140 | from uos import ilistdir 141 | from os import stat 142 | ls_all = ilistdir(directory) 143 | if debug: 144 | print(directory) 145 | print(str(ls_all)) 146 | # ls.sort() 147 | if goPrint: 148 | col = 0 149 | print("%8s %s " % ("d/[B]", "name")) 150 | for f in ls_all: 151 | if f[1] == 16384: 152 | # print(terminal_color(str(f[0]))) 153 | print("%8s %s " % ("---", terminal_color(str(f[0])))) 154 | 155 | if f[1] == 32768: 156 | # print(str(f[0]) + "" + str(stat(f[0])[6])) 157 | try: 158 | print( 159 | "%8s %s" % (str(stat(f[0])[6]), terminal_color(str(f[0]), 36))) 160 | except: 161 | # print("%8s %s" % ( str(stat(directory+f[0])[6]), terminal_color(str(f[0]),36))) 162 | print("%8s %s" % ("?", terminal_color(str(f[0]), 36))) 163 | 164 | """if line: 165 | print("%25s" % f,end="") 166 | col += 1 167 | if col % cols: 168 | print() 169 | else:""" 170 | print() 171 | return ls 172 | # globals()["ls"]=ls 173 | 174 | 175 | @command 176 | def cp(fileSource, fileTarget="/main.py"): 177 | from .terminal import printTitle, runningEffect 178 | printTitle("file_copy to " + fileTarget) 179 | print("(Always be careful)") 180 | fs = open(fileSource) 181 | data = fs.read() 182 | fs.close() 183 | 184 | runningEffect() 185 | ft = open(fileTarget, 'w') 186 | ft.write(data) 187 | ft.close() 188 | print(" ok") 189 | 190 | 191 | @command 192 | def mkdir(directory): 193 | try: 194 | from os import mkdir 195 | mkdir(directory) 196 | except Exception as e: 197 | print("Exception: {0}".format(e)) 198 | 199 | 200 | @command 201 | def rm(file=None): 202 | if file: 203 | from .terminal import printTitle, runningEffect 204 | printTitle("remove file > " + file) 205 | try: 206 | from os import remove 207 | print("(Always be careful)") 208 | remove(file) 209 | runningEffect() 210 | except Exception as e: 211 | print("Exception: {0}".format(e)) 212 | else: 213 | print("Input param: path + file name") 214 | 215 | 216 | @command 217 | def find(xstr, directory="examples"): # ?getcwd() 218 | from os import listdir 219 | from .terminal import printTitle 220 | printTitle("find file > " + xstr) 221 | ls = listdir(directory) 222 | ls.sort() 223 | for f in ls: 224 | if f.find(xstr) > -1: 225 | print(f) 226 | 227 | 228 | @command 229 | def df(echo=True): 230 | from os import statvfs 231 | if echo: 232 | print("> flash info: " + str(statvfs("/"))) 233 | flash_free = int(statvfs("/")[0]) * int(statvfs("/")[3]) 234 | if echo: 235 | print("> flash free: " + str(flash_free)) 236 | return flash_free 237 | 238 | 239 | @command 240 | def free(echo=True): 241 | from gc import mem_free 242 | if echo: 243 | print("--- RAM free ---> " + str(mem_free())) 244 | return mem_free() 245 | 246 | 247 | @command 248 | def top(): 249 | import os, ubinascii, machine 250 | from time import ticks_ms, ticks_diff 251 | from machine import RTC 252 | from gc import mem_free, mem_alloc 253 | import esp32 254 | 255 | from .terminal import terminal_color, printBar 256 | 257 | def add0(sn): 258 | ret_str = str(sn) 259 | if int(sn) < 10: 260 | ret_str = "0" + str(sn) 261 | return ret_str 262 | 263 | def get_hhmmss(separator=":"): 264 | rtc = RTC() # real time 265 | # get_hhmm(separator) | separator = string: "-" / " " 266 | hh = add0(rtc.datetime()[4]) 267 | mm = add0(rtc.datetime()[5]) 268 | ss = add0(rtc.datetime()[6]) 269 | return hh + separator + mm + separator + ss 270 | 271 | def f2c(Fahrenheit): 272 | Celsius = (Fahrenheit - 32) * 5.0/9.0 273 | return Celsius 274 | 275 | bar100 = 30 276 | print(terminal_color("-" * (bar100 + 20))) 277 | print(terminal_color("free Memory and Flash >")) 278 | # mem_alloc() * 100 279 | if mem_free() > 3000000: 280 | ram100 = 4093504 281 | else: 282 | ram100 = 95000 # hypotetic maximum 283 | 284 | b1 = ram100 / bar100 285 | ram = mem_free() 286 | print("RAM: ", end="") 287 | printBar(bar100 - int(ram / b1), int(ram / b1)) 288 | print(terminal_color(str(ram / 1000) + " kB")) 289 | 290 | flash100 = 2097152 291 | b1 = flash100 / bar100 292 | flash = df(False) 293 | print("Flash: ", end="") 294 | printBar(bar100 - int(flash / b1), int(flash / b1)) 295 | print(terminal_color(str(flash / 1000) + " kB")) 296 | 297 | print(terminal_color("-" * (bar100 + 20))) 298 | uid = ubinascii.hexlify(machine.unique_id()).decode() 299 | print(terminal_color("> ESP32 unique_id: ") + str(uid)) 300 | print(terminal_color("> uPy version: ") + str(os.uname()[3])) 301 | print(terminal_color("> octopusLAB shell: ") + __version__) 302 | 303 | print(terminal_color("-" * (bar100 + 20))) 304 | raw_c = int(f2c(esp32.raw_temperature())*10)/10 305 | print(terminal_color("> proc. raw_temperature: ") + terminal_color(str(raw_c) + " C", 31)) 306 | 307 | now = ticks_ms() 308 | for job_id, job_info in _background_jobs.items(): 309 | job_duration = ticks_diff(now, job_info['start_time']) 310 | job_command = ' '.join(job_info['command_list']) 311 | print( 312 | terminal_color( 313 | "[{job_id}] {job_duration}s {job_command}".format( 314 | job_id=job_id, 315 | job_duration=job_duration / 1000, 316 | job_command=job_command, 317 | ), 318 | 35 319 | ), 320 | ) 321 | print(terminal_color(get_hhmmss(), 36)) 322 | 323 | 324 | @command 325 | def wifi(comm="on"): 326 | global _wc 327 | 328 | # TODO: Remove depend libraries or document them 329 | if _wc is None: 330 | from utils.wifi_connect import WiFiConnect 331 | _wc = WiFiConnect() 332 | 333 | if comm == "on": 334 | if _wc.connect(): 335 | print("WiFi: OK") 336 | else: 337 | print("WiFi: Connect error, check configuration") 338 | 339 | if comm == "scan": 340 | staactive = _wc.sta_if.active() 341 | if not staactive: 342 | _wc.sta_if.active(True) 343 | 344 | from ubinascii import hexlify 345 | print("networks:") 346 | print('-' * SEPARATOR_WIDTH) 347 | nets = [[item[0].decode('utf-8'), hexlify(item[1], ":").decode(), item[2], item[3], item[4]] for item in _wc.sta_if.scan()] 348 | for net in nets: 349 | print(str(net)) 350 | print('-' * SEPARATOR_WIDTH) 351 | _wc.sta_if.active(staactive) 352 | 353 | if comm == "off": 354 | try: 355 | _wc.sta_if.disconnect() 356 | _wc.sta_if.active(False) 357 | except Exception as e: 358 | print("Exception: {0}".format(e)) 359 | 360 | 361 | @command 362 | def ping(host='google.com'): 363 | from lib.uping import ping 364 | try: 365 | ping(host) 366 | except OSError as e: 367 | if e.args[0] == -202: 368 | print("ping: {}: Name or service not known".format(host)) 369 | else: 370 | print("OSError, exception: {0}".format(e)) 371 | except Exception as e: 372 | print("Exception: {0}".format(e)) 373 | 374 | 375 | @command # TODO 376 | def upgrade(urlTar="https://octopusengine.org/download/micropython/stable.tar"): 377 | from ..setup import deploy 378 | from .terminal import printTitle 379 | printTitle("upgrade from url > ") 380 | print(urlTar) 381 | try: 382 | deploy(urlTar) 383 | except Exception as e: 384 | print("Exception: {0}".format(e)) 385 | 386 | @command 387 | def clear(): 388 | print(chr(27) + "[2J") # clear terminal 389 | print("\x1b[2J\x1b[H") # cursor up 390 | 391 | 392 | @command 393 | def run(filepath, *args): 394 | # exec(open(file).read(), globals()) 395 | exec(open(filepath).read(), { "_ARGS": args }) 396 | 397 | @command 398 | def ver(): 399 | print(__version__) 400 | 401 | 402 | @command 403 | def wgetapi(urlApi="https://www.octopusengine.org/api"): 404 | # https://www.octopusengine.org/api/message.php 405 | # get api text / jsoun / etc 406 | from urequests import get 407 | urltxt = urlApi + "/text123.txt" 408 | dt_str = "?" 409 | try: 410 | response = get(urltxt) 411 | dt_str = response.text 412 | except Exception as e: 413 | print("Err. read txt from URL", e) 414 | print(dt_str) 415 | 416 | 417 | @command 418 | def wget(url="https://www.octopusengine.org/api/text123.txt",path="download"): 419 | from .wget import wget 420 | wget(url, path) 421 | 422 | 423 | @command 424 | def pwd(): 425 | from uos import getcwd 426 | print(getcwd()) 427 | 428 | 429 | @command 430 | def cd(directory=""): 431 | from uos import chdir 432 | try: 433 | chdir(directory) 434 | except OSError: 435 | print("cd: {}: No such file or directory".format(directory)) 436 | 437 | 438 | @command 439 | def exit(): 440 | raise SystemExit 441 | 442 | 443 | @command 444 | def help(): 445 | print("octopusLAB - simple shell help:") 446 | cat("shell/octopus_shell_help.txt", False) 447 | print() 448 | 449 | 450 | class _release_cwd: 451 | def __enter__(self): 452 | from uos import getcwd 453 | self.current_directory = getcwd() 454 | 455 | def __exit__(self, type, value, traceback): 456 | from uos import chdir 457 | chdir(self.current_directory) 458 | 459 | 460 | def parse_input(input_str): 461 | command_list = input_str.strip().split() 462 | 463 | # support for background jobs via `&` at the end of line 464 | # TODO tests: 465 | # arguments = ['one', 'two', 'three&'] 466 | # arguments = ['one', 'two', 'three', '&'] 467 | # arguments = ['&'] 468 | # arguments = ['one&'] 469 | # arguments = ['one', 'two', 'three'] 470 | # arguments = ['one'] 471 | # arguments = [] 472 | if command_list and command_list[-1][-1] == '&': 473 | run_in_background = True 474 | command_list[-1] = command_list[-1][:-1] 475 | if not command_list[-1]: 476 | command_list = command_list[:-1] 477 | else: 478 | run_in_background = False 479 | 480 | return command_list, run_in_background 481 | 482 | 483 | def shell(): 484 | from uos import getcwd 485 | from sys import print_exception 486 | from .terminal import terminal_color 487 | with _release_cwd(): 488 | while True: 489 | try: 490 | input_str = input( 491 | terminal_color("uPyShell", 32) + ":~" + getcwd() + "$ " 492 | ) 493 | except KeyboardInterrupt: 494 | print('^C') 495 | continue 496 | except EOFError: 497 | print() 498 | return 499 | 500 | command_list, run_in_background = parse_input(input_str) 501 | 502 | if not command_list: 503 | continue 504 | 505 | # hacky support for run ./file.py 506 | if command_list[0][:2] == "./": 507 | cmd = command_list.pop(0) 508 | command_list = ['run', cmd[2:]] + command_list 509 | 510 | cmd, *arguments = command_list 511 | 512 | try: 513 | func = _command_registry[cmd] 514 | except KeyError: 515 | print('{cmd}: command not found'.format(cmd=cmd)) 516 | continue 517 | 518 | if run_in_background: 519 | func = _background_func(func, command_list) 520 | 521 | try: 522 | func(*arguments) 523 | except Exception as exc: 524 | print_exception(exc) 525 | except KeyboardInterrupt: 526 | print('^C') 527 | except SystemExit: 528 | return 529 | --------------------------------------------------------------------------------