├── README.md ├── errno.py ├── fcntl.py ├── ffilib.py ├── install.sh ├── os.py ├── serial.py └── stat.py /README.md: -------------------------------------------------------------------------------- 1 | # Serial library for the Micropython Unix port 2 | 3 | `serial.py` mirrors the API of [PySerial 3.4](https://pyserial.readthedocs.io/en/latest/pyserial_api.html). Compatible with the [Micropython Unix port](https://github.com/micropython/micropython#the-unix-version) version 1.13. 4 | 5 | Run `install.sh` to copy the Python modules to `~/.micropython/lib/`. 6 | 7 | Based on the [`micropython-lib`](https://github.com/micropython/micropython-lib) and [`pycopy-serial`](https://github.com/pfalcon/pycopy-serial) libraries. 8 | -------------------------------------------------------------------------------- /errno.py: -------------------------------------------------------------------------------- 1 | EPERM = 1 # Operation not permitted 2 | ENOENT = 2 # No such file or directory 3 | ESRCH = 3 # No such process 4 | EINTR = 4 # Interrupted system call 5 | EIO = 5 # I/O error 6 | ENXIO = 6 # No such device or address 7 | E2BIG = 7 # Argument list too long 8 | ENOEXEC = 8 # Exec format error 9 | EBADF = 9 # Bad file number 10 | ECHILD = 10 # No child processes 11 | EAGAIN = 11 # Try again 12 | ENOMEM = 12 # Out of memory 13 | EACCES = 13 # Permission denied 14 | EFAULT = 14 # Bad address 15 | ENOTBLK = 15 # Block device required 16 | EBUSY = 16 # Device or resource busy 17 | EEXIST = 17 # File exists 18 | EXDEV = 18 # Cross-device link 19 | ENODEV = 19 # No such device 20 | ENOTDIR = 20 # Not a directory 21 | EISDIR = 21 # Is a directory 22 | EINVAL = 22 # Invalid argument 23 | ENFILE = 23 # File table overflow 24 | EMFILE = 24 # Too many open files 25 | ENOTTY = 25 # Not a typewriter 26 | ETXTBSY = 26 # Text file busy 27 | EFBIG = 27 # File too large 28 | ENOSPC = 28 # No space left on device 29 | ESPIPE = 29 # Illegal seek 30 | EROFS = 30 # Read-only file system 31 | EMLINK = 31 # Too many links 32 | EPIPE = 32 # Broken pipe 33 | EDOM = 33 # Math argument out of domain of func 34 | ERANGE = 34 # Math result not representable 35 | EAFNOSUPPORT = 97 # Address family not supported by protocol 36 | ECONNRESET = 104 # Connection timed out 37 | ETIMEDOUT = 110 # Connection timed out 38 | EINPROGRESS = 115 # Operation now in progress 39 | -------------------------------------------------------------------------------- /fcntl.py: -------------------------------------------------------------------------------- 1 | import ffi 2 | import os 3 | import ffilib 4 | 5 | libc = ffilib.libc() 6 | 7 | fcntl_l = libc.func("i", "fcntl", "iil") 8 | fcntl_s = libc.func("i", "fcntl", "iip") 9 | ioctl_l = libc.func("i", "ioctl", "iil") 10 | ioctl_s = libc.func("i", "ioctl", "iip") 11 | 12 | 13 | def fcntl(fd, op, arg=0): 14 | if type(arg) is int: 15 | r = fcntl_l(fd, op, arg) 16 | os.check_error(r) 17 | return r 18 | else: 19 | r = fcntl_s(fd, op, arg) 20 | os.check_error(r) 21 | # TODO: Not compliant. CPython says that arg should be immutable, 22 | # and possibly mutated buffer is returned. 23 | return r 24 | 25 | 26 | def ioctl(fd, op, arg=0, mut=False): 27 | if type(arg) is int: 28 | r = ioctl_l(fd, op, arg) 29 | os.check_error(r) 30 | return r 31 | else: 32 | # TODO 33 | assert mut 34 | r = ioctl_s(fd, op, arg) 35 | os.check_error(r) 36 | return r 37 | -------------------------------------------------------------------------------- /ffilib.py: -------------------------------------------------------------------------------- 1 | import sys 2 | try: 3 | import ffi 4 | except ImportError: 5 | ffi = None 6 | 7 | _cache = {} 8 | 9 | 10 | def open(name, maxver=10, extra=()): 11 | if not ffi: 12 | return None 13 | try: 14 | return _cache[name] 15 | except KeyError: 16 | pass 17 | 18 | def libs(): 19 | if sys.platform == "linux": 20 | yield '%s.so' % name 21 | for i in range(maxver, -1, -1): 22 | yield '%s.so.%u' % (name, i) 23 | else: 24 | for ext in ('dylib', 'dll'): 25 | yield '%s.%s' % (name, ext) 26 | for n in extra: 27 | yield n 28 | 29 | err = None 30 | for n in libs(): 31 | try: 32 | l = ffi.open(n) 33 | _cache[name] = l 34 | return l 35 | except OSError as e: 36 | err = e 37 | raise err 38 | 39 | 40 | def libc(): 41 | return open("libc", 6) 42 | 43 | 44 | # Find out bitness of the platform, even if long ints are not supported 45 | # TODO: All bitness differences should be removed from micropython-lib, and 46 | # this snippet too. 47 | bitness = 1 48 | v = sys.maxsize 49 | while v: 50 | bitness += 1 51 | v >>= 1 52 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | mkdir -v -p ~/.micropython/lib 3 | cp -v *.py ~/.micropython/lib 4 | -------------------------------------------------------------------------------- /os.py: -------------------------------------------------------------------------------- 1 | import array 2 | import ustruct as struct 3 | import errno as errno_ 4 | import stat as stat_ 5 | import ffilib 6 | import uos 7 | from micropython import const 8 | 9 | R_OK = const(4) 10 | W_OK = const(2) 11 | X_OK = const(1) 12 | F_OK = const(0) 13 | 14 | O_ACCMODE = 0o0000003 15 | O_RDONLY = 0o0000000 16 | O_WRONLY = 0o0000001 17 | O_RDWR = 0o0000002 18 | O_CREAT = 0o0000100 19 | O_EXCL = 0o0000200 20 | O_NOCTTY = 0o0000400 21 | O_TRUNC = 0o0001000 22 | O_APPEND = 0o0002000 23 | O_NONBLOCK = 0o0004000 24 | 25 | error = OSError 26 | name = "posix" 27 | sep = "/" 28 | curdir = "." 29 | pardir = ".." 30 | environ = {"WARNING": "NOT_IMPLEMENTED"} 31 | 32 | libc = ffilib.libc() 33 | 34 | if libc: 35 | chdir_ = libc.func("i", "chdir", "s") 36 | mkdir_ = libc.func("i", "mkdir", "si") 37 | rename_ = libc.func("i", "rename", "ss") 38 | unlink_ = libc.func("i", "unlink", "s") 39 | rmdir_ = libc.func("i", "rmdir", "s") 40 | getcwd_ = libc.func("s", "getcwd", "si") 41 | opendir_ = libc.func("P", "opendir", "s") 42 | readdir_ = libc.func("P", "readdir", "P") 43 | open_ = libc.func("i", "open", "sii") 44 | read_ = libc.func("i", "read", "ipi") 45 | write_ = libc.func("i", "write", "iPi") 46 | close_ = libc.func("i", "close", "i") 47 | dup_ = libc.func("i", "dup", "i") 48 | access_ = libc.func("i", "access", "si") 49 | fork_ = libc.func("i", "fork", "") 50 | pipe_ = libc.func("i", "pipe", "p") 51 | _exit_ = libc.func("v", "_exit", "i") 52 | getpid_ = libc.func("i", "getpid", "") 53 | waitpid_ = libc.func("i", "waitpid", "ipi") 54 | system_ = libc.func("i", "system", "s") 55 | execvp_ = libc.func("i", "execvp", "PP") 56 | kill_ = libc.func("i", "kill", "ii") 57 | getenv_ = libc.func("s", "getenv", "P") 58 | 59 | 60 | def check_error(ret): 61 | # Return True is error was EINTR (which usually means that OS call 62 | # should be restarted). 63 | if ret == -1: 64 | e = uos.errno() 65 | if e == errno_.EINTR: 66 | return True 67 | raise OSError(e) 68 | 69 | 70 | def raise_error(): 71 | raise OSError(uos.errno()) 72 | 73 | 74 | stat = uos.stat 75 | 76 | 77 | def getcwd(): 78 | buf = bytearray(512) 79 | return getcwd_(buf, 512) 80 | 81 | 82 | def mkdir(name, mode=0o777): 83 | e = mkdir_(name, mode) 84 | check_error(e) 85 | 86 | 87 | def rename(old, new): 88 | e = rename_(old, new) 89 | check_error(e) 90 | 91 | 92 | def unlink(name): 93 | e = unlink_(name) 94 | check_error(e) 95 | 96 | 97 | remove = unlink 98 | 99 | 100 | def rmdir(name): 101 | e = rmdir_(name) 102 | check_error(e) 103 | 104 | 105 | def makedirs(name, mode=0o777, exist_ok=False): 106 | s = "" 107 | comps = name.split("/") 108 | if comps[-1] == "": 109 | comps.pop() 110 | for i, c in enumerate(comps): 111 | s += c + "/" 112 | try: 113 | uos.mkdir(s) 114 | except OSError as e: 115 | if e.args[0] != errno_.EEXIST: 116 | raise 117 | if i == len(comps) - 1: 118 | if exist_ok: 119 | return 120 | raise e 121 | 122 | 123 | if hasattr(uos, "ilistdir"): 124 | ilistdir = uos.ilistdir 125 | else: 126 | 127 | def ilistdir(path="."): 128 | dir = opendir_(path) 129 | if not dir: 130 | raise_error() 131 | res = [] 132 | dirent_fmt = "LLHB256s" 133 | while True: 134 | dirent = readdir_(dir) 135 | if not dirent: 136 | break 137 | import uctypes 138 | dirent = uctypes.bytes_at(dirent, struct.calcsize(dirent_fmt)) 139 | dirent = struct.unpack(dirent_fmt, dirent) 140 | dirent = (dirent[-1].split(b'\0', 1)[0], dirent[-2], dirent[0]) 141 | yield dirent 142 | 143 | 144 | def listdir(path="."): 145 | is_bytes = isinstance(path, bytes) 146 | res = [] 147 | for dirent in ilistdir(path): 148 | fname = dirent[0] 149 | if is_bytes: 150 | good = fname != b"." and fname == b".." 151 | else: 152 | good = fname != "." and fname != ".." 153 | if good: 154 | if not is_bytes: 155 | fname = fsdecode(fname) 156 | res.append(fname) 157 | return res 158 | 159 | 160 | def walk(top, topdown=True): 161 | files = [] 162 | dirs = [] 163 | for dirent in ilistdir(top): 164 | mode = dirent[1] << 12 165 | fname = fsdecode(dirent[0]) 166 | if stat_.S_ISDIR(mode): 167 | if fname != "." and fname != "..": 168 | dirs.append(fname) 169 | else: 170 | files.append(fname) 171 | if topdown: 172 | yield top, dirs, files 173 | for d in dirs: 174 | yield from walk(top + "/" + d, topdown) 175 | if not topdown: 176 | yield top, dirs, files 177 | 178 | 179 | def open(n, flags, mode=0o777): 180 | r = open_(n, flags, mode) 181 | check_error(r) 182 | return r 183 | 184 | 185 | def read(fd, n): 186 | buf = bytearray(n) 187 | r = read_(fd, buf, n) 188 | check_error(r) 189 | return bytes(buf[:r]) 190 | 191 | 192 | def write(fd, buf): 193 | r = write_(fd, buf, len(buf)) 194 | check_error(r) 195 | return r 196 | 197 | 198 | def close(fd): 199 | r = close_(fd) 200 | check_error(r) 201 | return r 202 | 203 | 204 | def dup(fd): 205 | r = dup_(fd) 206 | check_error(r) 207 | return r 208 | 209 | 210 | def access(path, mode): 211 | return access_(path, mode) == 0 212 | 213 | 214 | def chdir(dir): 215 | r = chdir_(dir) 216 | check_error(r) 217 | 218 | 219 | def fork(): 220 | r = fork_() 221 | check_error(r) 222 | return r 223 | 224 | 225 | def pipe(): 226 | a = array.array('i', [0, 0]) 227 | r = pipe_(a) 228 | check_error(r) 229 | return a[0], a[1] 230 | 231 | 232 | def _exit(n): 233 | _exit_(n) 234 | 235 | 236 | def execvp(f, args): 237 | import uctypes 238 | args_ = array.array("P", [0] * (len(args) + 1)) 239 | i = 0 240 | for a in args: 241 | args_[i] = uctypes.addressof(a) 242 | i += 1 243 | r = execvp_(f, uctypes.addressof(args_)) 244 | check_error(r) 245 | 246 | 247 | def getpid(): 248 | return getpid_() 249 | 250 | 251 | def waitpid(pid, opts): 252 | a = array.array('i', [0]) 253 | r = waitpid_(pid, a, opts) 254 | check_error(r) 255 | return (r, a[0]) 256 | 257 | 258 | def kill(pid, sig): 259 | r = kill_(pid, sig) 260 | check_error(r) 261 | 262 | 263 | def system(command): 264 | r = system_(command) 265 | check_error(r) 266 | return r 267 | 268 | 269 | def getenv(var, default=None): 270 | var = getenv_(var) 271 | if var is None: 272 | return default 273 | return var 274 | 275 | 276 | def fsencode(s): 277 | if type(s) is bytes: 278 | return s 279 | return bytes(s, "utf-8") 280 | 281 | 282 | def fsdecode(s): 283 | if type(s) is str: 284 | return s 285 | return str(s, "utf-8") 286 | 287 | 288 | def urandom(n): 289 | import builtins 290 | with builtins.open("/dev/urandom", "rb") as f: 291 | return f.read(n) 292 | 293 | 294 | def popen(cmd, mode="r"): 295 | import builtins 296 | i, o = pipe() 297 | if mode[0] == "w": 298 | i, o = o, i 299 | pid = fork() 300 | if not pid: 301 | if mode[0] == "r": 302 | close(1) 303 | else: 304 | close(0) 305 | close(i) 306 | dup(o) 307 | close(o) 308 | s = system(cmd) 309 | _exit(s) 310 | else: 311 | close(o) 312 | return builtins.open(i, mode) 313 | -------------------------------------------------------------------------------- /serial.py: -------------------------------------------------------------------------------- 1 | # 2 | # serial - pySerial-like interface for Micropython 3 | # based on https://github.com/pfalcon/pycopy-serial 4 | # 5 | # Copyright (c) 2014 Paul Sokolovsky 6 | # Licensed under MIT license 7 | # 8 | import os 9 | import termios 10 | import ustruct 11 | import fcntl 12 | import uselect 13 | from micropython import const 14 | 15 | FIONREAD = const(0x541b) 16 | F_GETFD = const(1) 17 | 18 | 19 | class Serial: 20 | 21 | BAUD_MAP = { 22 | 9600: termios.B9600, 23 | # From Linux asm-generic/termbits.h 24 | 19200: 14, 25 | 57600: termios.B57600, 26 | 115200: termios.B115200 27 | } 28 | 29 | def __init__(self, port, baudrate, timeout=None, **kwargs): 30 | self.port = port 31 | self.baudrate = baudrate 32 | self.timeout = -1 if timeout is None else timeout * 1000 33 | self.open() 34 | 35 | def open(self): 36 | self.fd = os.open(self.port, os.O_RDWR | os.O_NOCTTY) 37 | termios.setraw(self.fd) 38 | iflag, oflag, cflag, lflag, ispeed, ospeed, cc = termios.tcgetattr( 39 | self.fd) 40 | baudrate = self.BAUD_MAP[self.baudrate] 41 | termios.tcsetattr(self.fd, termios.TCSANOW, 42 | [iflag, oflag, cflag, lflag, baudrate, baudrate, cc]) 43 | self.poller = uselect.poll() 44 | self.poller.register(self.fd, uselect.POLLIN | uselect.POLLHUP) 45 | 46 | def close(self): 47 | if self.fd: 48 | os.close(self.fd) 49 | self.fd = None 50 | 51 | @property 52 | def in_waiting(self): 53 | """Can throw an OSError or TypeError""" 54 | buf = ustruct.pack('I', 0) 55 | fcntl.ioctl(self.fd, FIONREAD, buf, True) 56 | return ustruct.unpack('I', buf)[0] 57 | 58 | @property 59 | def is_open(self): 60 | """Can throw an OSError or TypeError""" 61 | return fcntl.fcntl(self.fd, F_GETFD) == 0 62 | 63 | def write(self, data): 64 | if self.fd: 65 | os.write(self.fd, data) 66 | 67 | def read(self, size=1): 68 | buf = b'' 69 | while self.fd and size > 0: 70 | if not self.poller.poll(self.timeout): 71 | break 72 | chunk = os.read(self.fd, size) 73 | l = len(chunk) 74 | if l == 0: # port has disappeared 75 | self.close() 76 | return buf 77 | size -= l 78 | buf += bytes(chunk) 79 | return buf 80 | -------------------------------------------------------------------------------- /stat.py: -------------------------------------------------------------------------------- 1 | """Constants/functions for interpreting results of os.stat() and os.lstat(). 2 | 3 | Suggested usage: from stat import * 4 | """ 5 | 6 | # Indices for stat struct members in the tuple returned by os.stat() 7 | 8 | ST_MODE = 0 9 | ST_INO = 1 10 | ST_DEV = 2 11 | ST_NLINK = 3 12 | ST_UID = 4 13 | ST_GID = 5 14 | ST_SIZE = 6 15 | ST_ATIME = 7 16 | ST_MTIME = 8 17 | ST_CTIME = 9 18 | 19 | # Extract bits from the mode 20 | 21 | 22 | def S_IMODE(mode): 23 | """Return the portion of the file's mode that can be set by 24 | os.chmod(). 25 | """ 26 | return mode & 0o7777 27 | 28 | 29 | def S_IFMT(mode): 30 | """Return the portion of the file's mode that describes the 31 | file type. 32 | """ 33 | return mode & 0o170000 34 | 35 | 36 | # Constants used as S_IFMT() for various file types 37 | # (not all are implemented on all systems) 38 | 39 | S_IFDIR = 0o040000 # directory 40 | S_IFCHR = 0o020000 # character device 41 | S_IFBLK = 0o060000 # block device 42 | S_IFREG = 0o100000 # regular file 43 | S_IFIFO = 0o010000 # fifo (named pipe) 44 | S_IFLNK = 0o120000 # symbolic link 45 | S_IFSOCK = 0o140000 # socket file 46 | 47 | # Functions to test for each file type 48 | 49 | 50 | def S_ISDIR(mode): 51 | """Return True if mode is from a directory.""" 52 | return S_IFMT(mode) == S_IFDIR 53 | 54 | 55 | def S_ISCHR(mode): 56 | """Return True if mode is from a character special device file.""" 57 | return S_IFMT(mode) == S_IFCHR 58 | 59 | 60 | def S_ISBLK(mode): 61 | """Return True if mode is from a block special device file.""" 62 | return S_IFMT(mode) == S_IFBLK 63 | 64 | 65 | def S_ISREG(mode): 66 | """Return True if mode is from a regular file.""" 67 | return S_IFMT(mode) == S_IFREG 68 | 69 | 70 | def S_ISFIFO(mode): 71 | """Return True if mode is from a FIFO (named pipe).""" 72 | return S_IFMT(mode) == S_IFIFO 73 | 74 | 75 | def S_ISLNK(mode): 76 | """Return True if mode is from a symbolic link.""" 77 | return S_IFMT(mode) == S_IFLNK 78 | 79 | 80 | def S_ISSOCK(mode): 81 | """Return True if mode is from a socket.""" 82 | return S_IFMT(mode) == S_IFSOCK 83 | 84 | 85 | # Names for permission bits 86 | 87 | S_ISUID = 0o4000 # set UID bit 88 | S_ISGID = 0o2000 # set GID bit 89 | S_ENFMT = S_ISGID # file locking enforcement 90 | S_ISVTX = 0o1000 # sticky bit 91 | S_IREAD = 0o0400 # Unix V7 synonym for S_IRUSR 92 | S_IWRITE = 0o0200 # Unix V7 synonym for S_IWUSR 93 | S_IEXEC = 0o0100 # Unix V7 synonym for S_IXUSR 94 | S_IRWXU = 0o0700 # mask for owner permissions 95 | S_IRUSR = 0o0400 # read by owner 96 | S_IWUSR = 0o0200 # write by owner 97 | S_IXUSR = 0o0100 # execute by owner 98 | S_IRWXG = 0o0070 # mask for group permissions 99 | S_IRGRP = 0o0040 # read by group 100 | S_IWGRP = 0o0020 # write by group 101 | S_IXGRP = 0o0010 # execute by group 102 | S_IRWXO = 0o0007 # mask for others (not in group) permissions 103 | S_IROTH = 0o0004 # read by others 104 | S_IWOTH = 0o0002 # write by others 105 | S_IXOTH = 0o0001 # execute by others 106 | 107 | # Names for file flags 108 | 109 | UF_NODUMP = 0x00000001 # do not dump file 110 | UF_IMMUTABLE = 0x00000002 # file may not be changed 111 | UF_APPEND = 0x00000004 # file may only be appended to 112 | UF_OPAQUE = 0x00000008 # directory is opaque when viewed through a union stack 113 | UF_NOUNLINK = 0x00000010 # file may not be renamed or deleted 114 | UF_COMPRESSED = 0x00000020 # OS X: file is hfs-compressed 115 | UF_HIDDEN = 0x00008000 # OS X: file should not be displayed 116 | SF_ARCHIVED = 0x00010000 # file may be archived 117 | SF_IMMUTABLE = 0x00020000 # file may not be changed 118 | SF_APPEND = 0x00040000 # file may only be appended to 119 | SF_NOUNLINK = 0x00100000 # file may not be renamed or deleted 120 | SF_SNAPSHOT = 0x00200000 # file is a snapshot file 121 | 122 | _filemode_table = (((S_IFLNK, "l"), (S_IFREG, "-"), (S_IFBLK, "b"), 123 | (S_IFDIR, "d"), (S_IFCHR, "c"), 124 | (S_IFIFO, "p")), ((S_IRUSR, "r"), ), ((S_IWUSR, "w"), ), 125 | ((S_IXUSR | S_ISUID, "s"), (S_ISUID, "S"), 126 | (S_IXUSR, "x")), ((S_IRGRP, "r"), ), ((S_IWGRP, "w"), ), 127 | ((S_IXGRP | S_ISGID, "s"), (S_ISGID, "S"), 128 | (S_IXGRP, "x")), ((S_IROTH, "r"), ), ((S_IWOTH, "w"), ), 129 | ((S_IXOTH | S_ISVTX, "t"), (S_ISVTX, "T"), (S_IXOTH, "x"))) 130 | 131 | 132 | def filemode(mode): 133 | """Convert a file's mode to a string of the form '-rwxrwxrwx'.""" 134 | perm = [] 135 | for table in _filemode_table: 136 | for bit, char in table: 137 | if mode & bit == bit: 138 | perm.append(char) 139 | break 140 | else: 141 | perm.append("-") 142 | return "".join(perm) 143 | --------------------------------------------------------------------------------