├── up └── test.txt ├── main.py ├── .gitignore ├── boot.py ├── class_log.py ├── README.md ├── class_pcb.py ├── uasyncio ├── synchro.py ├── queues.py ├── __init__.py └── core.py ├── app_sim.py └── class_sim800.py /up/test.txt: -------------------------------------------------------------------------------- 1 | ola from olab.lt ;] -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from class_log import log 2 | from class_pcb import pcb 3 | import app_sim as app 4 | 5 | print("pcb.init...") 6 | pcb.init() 7 | 8 | print("app.run...") 9 | app.run() -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # Distribution / packaging 6 | .Python 7 | env/ 8 | build/ 9 | dist/ 10 | downloads/ 11 | docs/ 12 | lib/ 13 | lib64/ 14 | parts/ 15 | var/ 16 | -------------------------------------------------------------------------------- /boot.py: -------------------------------------------------------------------------------- 1 | print("[INFO] boot.py") 2 | 3 | import machine 4 | import pyb 5 | 6 | # pyb.main('main.py') # main script to run after this one 7 | #pyb.usb_mode('VCP+MSC') # act as a serial and a storage device 8 | #pyb.usb_mode('VCP+HID') # act as a serial device and a mouse 9 | -------------------------------------------------------------------------------- /class_log.py: -------------------------------------------------------------------------------- 1 | 2 | class log: 3 | @classmethod 4 | def info(cls, message): 5 | print("[INFO] {}".format(message)) 6 | 7 | @classmethod 8 | def debug(cls, message): 9 | print("[DEBUG] {}".format(message)) 10 | 11 | @classmethod 12 | def error(cls, message): 13 | print("[ERROR] {}".format(message)) 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # micropython driver for sim800 3 | 4 | ## Uploading the code - rshell 5 | 6 | ```bash 7 | rshell rsync --mirror --verbose ./ /flash 8 | ``` 9 | 10 | ## References 11 | 12 | - http://m2msupport.net/m2msupport/tutorial-for-simcom-m2m-modules/ 13 | - https://arduino.stackexchange.com/questions/23878/what-is-the-proper-way-to-send-data-through-http-using-sim908?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa&newreg=e3fa216955c543bdb6b638d5fda21c48 14 | - SIM800 TCP https://cdn-shop.adafruit.com/product-files/2637/SIM800+Series_TCPIP_Application+Note_V1.01.pdf 15 | -------------------------------------------------------------------------------- /class_pcb.py: -------------------------------------------------------------------------------- 1 | from pyb import Pin, LED, ADC 2 | class pcb: 3 | @classmethod 4 | def init(cls): 5 | cls._gps = Pin('X5', mode=Pin.OUT_PP, pull=Pin.PULL_DOWN) 6 | cls._gsm = Pin('X6', mode=Pin.OUT_PP, pull=Pin.PULL_DOWN) 7 | cls._gps.value(True) 8 | cls._gsm.value(True) 9 | 10 | cls._red = LED(1) 11 | cls._blue = LED(4) 12 | 13 | cls._batt = ADC('X7') # create an analog object from a pin 14 | 15 | @classmethod 16 | def red_on(cls): 17 | cls._red.on() 18 | 19 | @classmethod 20 | def red_off(cls): 21 | cls._red.off() 22 | 23 | 24 | @classmethod 25 | def get_voltage(cls): 26 | v = 4.13 / 2592 * cls._batt.read() # read an analog value 27 | return v -------------------------------------------------------------------------------- /uasyncio/synchro.py: -------------------------------------------------------------------------------- 1 | from uasyncio import core 2 | 3 | class Lock: 4 | 5 | def __init__(self): 6 | self.locked = False 7 | self.wlist = [] 8 | 9 | def release(self): 10 | assert self.locked 11 | self.locked = False 12 | if self.wlist: 13 | #print(self.wlist) 14 | coro = self.wlist.pop(0) 15 | core.get_event_loop().call_soon(coro) 16 | 17 | def acquire(self): 18 | # As release() is not coro, assume we just released and going to acquire again 19 | # so, yield first to let someone else to acquire it first 20 | yield 21 | #print("acquire:", self.locked) 22 | while 1: 23 | if not self.locked: 24 | self.locked = True 25 | return True 26 | #print("putting", core.get_event_loop().cur_task, "on waiting list") 27 | self.wlist.append(core.get_event_loop().cur_task) 28 | yield False 29 | -------------------------------------------------------------------------------- /app_sim.py: -------------------------------------------------------------------------------- 1 | import uasyncio as asyncio 2 | from class_sim800 import sim_uart 3 | from class_log import log 4 | 5 | async def app_loop(): 6 | 7 | log.info("sim_uart.init...") 8 | sim = sim_uart() 9 | 10 | log.info("sim.command...") 11 | await sim.command("ATZ", "OK") # reset to default 12 | await sim.command('AT+CMEE=2', "OK") # enable error strings 13 | await sim.command("AT+CBC", "+CBC") # battery status 14 | 15 | apn = "internet.tele2.lt" 16 | url = 'http://exploreembedded.com/wiki/images/1/15/Hello.txt' 17 | 18 | await sim.command('AT+CREG?') # Check the Network Registration 19 | await sim.command('AT+SAPBR=3,1,"Contype","GPRS"', 'OK') # connection type 20 | await sim.command('AT+SAPBR=3,1,"APN","{}"'.format(apn), 'OK') # GPRS APN 21 | await sim.command('AT+SAPBR=2,1') # Get current bearer 22 | 23 | await sim.command('AT+SAPBR=1,1', 'OK') # Open GPRS connection on profile 1 24 | await sim.command('AT+HTTPINIT', 'OK') # Init HTTP service 25 | await sim.command('AT+HTTPPARA="CID",1', 'OK') # Choose profile 1 as HTTP channel 26 | await sim.command('AT+HTTPPARA="URL","{}"'.format(url), 'OK') 27 | # await sim.command('AT+HTTPSSL=1') # Enable HTTPS function 28 | # await sim.command('AT+HTTPPARA="REDIR",1', 'OK') 29 | await sim.command('AT+HTTPACTION=0', '+HTTPACTION') # start GET session (may be long) 30 | await sim.command('AT+HTTPREAD') # Read the data of HTTP server 31 | await sim.command('AT+HTTPTERM', 'OK') # terminate HTTP task 32 | await sim.command('AT+SAPBR=0,1', 'OK') # close Bearer context 33 | 34 | log.info("done.") 35 | 36 | 37 | def run(): 38 | loop = asyncio.get_event_loop() 39 | loop.create_task(app_loop()) 40 | log.info("loop.run_forever...") 41 | loop.run_forever() 42 | 43 | -------------------------------------------------------------------------------- /class_sim800.py: -------------------------------------------------------------------------------- 1 | 2 | from pyb import UART 3 | import uasyncio as asyncio 4 | 5 | import utime as time 6 | 7 | class sim_uart(): 8 | def __init__(self, uart_no = 2, timeout=4000): 9 | self.uart = UART(uart_no, 9600, timeout=timeout) 10 | self.loop = asyncio.get_event_loop() 11 | self.deadline = None 12 | self.result = '' 13 | 14 | async def writeline(self, command): 15 | self.uart.write("{}\r\n".format(command)) 16 | print("<", command) 17 | 18 | async def write(self, command): 19 | self.uart.write("{}".format(command)) 20 | print("<", command) 21 | 22 | 23 | 24 | def stop(self, in_advance=False): 25 | if not in_advance: 26 | print("no time left - deadline") 27 | else: 28 | print("stopped in advance - found expected string") 29 | self.deadline = None 30 | 31 | def running(self): 32 | return self.deadline is not None 33 | 34 | def postpone(self, duration = 1000): 35 | self.deadline = time.ticks_add(time.ticks_ms(), duration) 36 | 37 | def read(self, expect=None, duration=1000): 38 | self.result = '' 39 | self.postpone(duration) 40 | self.loop.create_task(self.read_killer(expect, duration)) 41 | 42 | async def read_killer(self, expect=None, duration=1000): 43 | time_left = time.ticks_diff(self.deadline, time.ticks_ms()) 44 | while time_left > 0: 45 | line = self.uart.readline() 46 | if line: 47 | line = convert_to_string(line) 48 | print(">", line) 49 | self.result += line 50 | if expect and line.find(expect)==0: 51 | # if expect and expect in line: 52 | self.stop(True) 53 | return True 54 | self.postpone(duration) 55 | time_left = time.ticks_diff(self.deadline, time.ticks_ms()) 56 | self.stop() 57 | 58 | 59 | async def command(self, command, expect=None, duration=1000): 60 | await self.writeline(command) 61 | 62 | self.read(expect, duration) 63 | while self.running(): 64 | await asyncio.sleep(0.2) # Pause 0.2s 65 | 66 | result = self.result 67 | return result 68 | 69 | def convert_to_string(buf): 70 | try: 71 | tt = buf.decode('utf-8').strip() 72 | return tt 73 | except UnicodeError: 74 | tmp = bytearray(buf) 75 | for i in range(len(tmp)): 76 | if tmp[i]>127: 77 | tmp[i] = ord('#') 78 | return bytes(tmp).decode('utf-8').strip() 79 | -------------------------------------------------------------------------------- /uasyncio/queues.py: -------------------------------------------------------------------------------- 1 | from collections.deque import deque 2 | from uasyncio.core import sleep 3 | 4 | 5 | class QueueEmpty(Exception): 6 | """Exception raised by get_nowait().""" 7 | 8 | 9 | class QueueFull(Exception): 10 | """Exception raised by put_nowait().""" 11 | 12 | 13 | class Queue: 14 | """A queue, useful for coordinating producer and consumer coroutines. 15 | 16 | If maxsize is less than or equal to zero, the queue size is infinite. If it 17 | is an integer greater than 0, then "yield from put()" will block when the 18 | queue reaches maxsize, until an item is removed by get(). 19 | 20 | Unlike the standard library Queue, you can reliably know this Queue's size 21 | with qsize(), since your single-threaded uasyncio application won't be 22 | interrupted between calling qsize() and doing an operation on the Queue. 23 | """ 24 | _attempt_delay = 0.1 25 | 26 | def __init__(self, maxsize=0): 27 | self.maxsize = maxsize 28 | self._queue = deque() 29 | 30 | def _get(self): 31 | return self._queue.popleft() 32 | 33 | def get(self): 34 | """Returns generator, which can be used for getting (and removing) 35 | an item from a queue. 36 | 37 | Usage:: 38 | 39 | item = yield from queue.get() 40 | """ 41 | while not self._queue: 42 | yield from sleep(self._attempt_delay) 43 | return self._get() 44 | 45 | def get_nowait(self): 46 | """Remove and return an item from the queue. 47 | 48 | Return an item if one is immediately available, else raise QueueEmpty. 49 | """ 50 | if not self._queue: 51 | raise QueueEmpty() 52 | return self._get() 53 | 54 | def _put(self, val): 55 | self._queue.append(val) 56 | 57 | def put(self, val): 58 | """Returns generator which can be used for putting item in a queue. 59 | 60 | Usage:: 61 | 62 | yield from queue.put(item) 63 | """ 64 | while self.qsize() >= self.maxsize and self.maxsize: 65 | yield from sleep(self._attempt_delay) 66 | self._put(val) 67 | 68 | def put_nowait(self, val): 69 | """Put an item into the queue without blocking. 70 | 71 | If no free slot is immediately available, raise QueueFull. 72 | """ 73 | if self.qsize() >= self.maxsize and self.maxsize: 74 | raise QueueFull() 75 | self._put(val) 76 | 77 | def qsize(self): 78 | """Number of items in the queue.""" 79 | return len(self._queue) 80 | 81 | def empty(self): 82 | """Return True if the queue is empty, False otherwise.""" 83 | return not self._queue 84 | 85 | def full(self): 86 | """Return True if there are maxsize items in the queue. 87 | 88 | Note: if the Queue was initialized with maxsize=0 (the default), 89 | then full() is never True. 90 | """ 91 | if self.maxsize <= 0: 92 | return False 93 | else: 94 | return self.qsize() >= self.maxsize 95 | -------------------------------------------------------------------------------- /uasyncio/__init__.py: -------------------------------------------------------------------------------- 1 | import uerrno 2 | import uselect as select 3 | import usocket as _socket 4 | from uasyncio.core import * 5 | 6 | 7 | DEBUG = 0 8 | log = None 9 | 10 | def set_debug(val): 11 | global DEBUG, log 12 | DEBUG = val 13 | if val: 14 | import logging 15 | log = logging.getLogger("uasyncio") 16 | 17 | 18 | class PollEventLoop(EventLoop): 19 | 20 | def __init__(self, runq_len=16, waitq_len=16): 21 | EventLoop.__init__(self, runq_len, waitq_len) 22 | self.poller = select.poll() 23 | self.objmap = {} 24 | 25 | def add_reader(self, sock, cb, *args): 26 | if DEBUG and __debug__: 27 | log.debug("add_reader%s", (sock, cb, args)) 28 | if args: 29 | self.poller.register(sock, select.POLLIN) 30 | self.objmap[id(sock)] = (cb, args) 31 | else: 32 | self.poller.register(sock, select.POLLIN) 33 | self.objmap[id(sock)] = cb 34 | 35 | def remove_reader(self, sock): 36 | if DEBUG and __debug__: 37 | log.debug("remove_reader(%s)", sock) 38 | self.poller.unregister(sock) 39 | del self.objmap[id(sock)] 40 | 41 | def add_writer(self, sock, cb, *args): 42 | if DEBUG and __debug__: 43 | log.debug("add_writer%s", (sock, cb, args)) 44 | if args: 45 | self.poller.register(sock, select.POLLOUT) 46 | self.objmap[id(sock)] = (cb, args) 47 | else: 48 | self.poller.register(sock, select.POLLOUT) 49 | self.objmap[id(sock)] = cb 50 | 51 | def remove_writer(self, sock): 52 | if DEBUG and __debug__: 53 | log.debug("remove_writer(%s)", sock) 54 | try: 55 | self.poller.unregister(sock) 56 | self.objmap.pop(id(sock), None) 57 | except OSError as e: 58 | # StreamWriter.awrite() first tries to write to a socket, 59 | # and if that succeeds, yield IOWrite may never be called 60 | # for that socket, and it will never be added to poller. So, 61 | # ignore such error. 62 | if e.args[0] != uerrno.ENOENT: 63 | raise 64 | 65 | def wait(self, delay): 66 | if DEBUG and __debug__: 67 | log.debug("poll.wait(%d)", delay) 68 | # We need one-shot behavior (second arg of 1 to .poll()) 69 | res = self.poller.ipoll(delay, 1) 70 | #log.debug("poll result: %s", res) 71 | # Remove "if res" workaround after 72 | # https://github.com/micropython/micropython/issues/2716 fixed. 73 | if res: 74 | for sock, ev in res: 75 | cb = self.objmap[id(sock)] 76 | if ev & (select.POLLHUP | select.POLLERR): 77 | # These events are returned even if not requested, and 78 | # are sticky, i.e. will be returned again and again. 79 | # If the caller doesn't do proper error handling and 80 | # unregister this sock, we'll busy-loop on it, so we 81 | # as well can unregister it now "just in case". 82 | self.remove_reader(sock) 83 | if DEBUG and __debug__: 84 | log.debug("Calling IO callback: %r", cb) 85 | if isinstance(cb, tuple): 86 | cb[0](*cb[1]) 87 | else: 88 | cb.pend_throw(None) 89 | self.call_soon(cb) 90 | 91 | 92 | class StreamReader: 93 | 94 | def __init__(self, polls, ios=None): 95 | if ios is None: 96 | ios = polls 97 | self.polls = polls 98 | self.ios = ios 99 | 100 | def read(self, n=-1): 101 | while True: 102 | yield IORead(self.polls) 103 | res = self.ios.read(n) 104 | if res is not None: 105 | break 106 | # This should not happen for real sockets, but can easily 107 | # happen for stream wrappers (ssl, websockets, etc.) 108 | #log.warn("Empty read") 109 | if not res: 110 | yield IOReadDone(self.polls) 111 | return res 112 | 113 | def readexactly(self, n): 114 | buf = b"" 115 | while n: 116 | yield IORead(self.polls) 117 | res = self.ios.read(n) 118 | assert res is not None 119 | if not res: 120 | yield IOReadDone(self.polls) 121 | break 122 | buf += res 123 | n -= len(res) 124 | return buf 125 | 126 | def readline(self): 127 | if DEBUG and __debug__: 128 | log.debug("StreamReader.readline()") 129 | buf = b"" 130 | while True: 131 | yield IORead(self.polls) 132 | res = self.ios.readline() 133 | assert res is not None 134 | if not res: 135 | yield IOReadDone(self.polls) 136 | break 137 | buf += res 138 | if buf[-1] == 0x0a: 139 | break 140 | if DEBUG and __debug__: 141 | log.debug("StreamReader.readline(): %s", buf) 142 | return buf 143 | 144 | def aclose(self): 145 | yield IOReadDone(self.polls) 146 | self.ios.close() 147 | 148 | def __repr__(self): 149 | return "" % (self.polls, self.ios) 150 | 151 | 152 | class StreamWriter: 153 | 154 | def __init__(self, s, extra): 155 | self.s = s 156 | self.extra = extra 157 | 158 | def awrite(self, buf, off=0, sz=-1): 159 | # This method is called awrite (async write) to not proliferate 160 | # incompatibility with original asyncio. Unlike original asyncio 161 | # whose .write() method is both not a coroutine and guaranteed 162 | # to return immediately (which means it has to buffer all the 163 | # data), this method is a coroutine. 164 | if sz == -1: 165 | sz = len(buf) - off 166 | if DEBUG and __debug__: 167 | log.debug("StreamWriter.awrite(): spooling %d bytes", sz) 168 | while True: 169 | res = self.s.write(buf, off, sz) 170 | # If we spooled everything, return immediately 171 | if res == sz: 172 | if DEBUG and __debug__: 173 | log.debug("StreamWriter.awrite(): completed spooling %d bytes", res) 174 | return 175 | if res is None: 176 | res = 0 177 | if DEBUG and __debug__: 178 | log.debug("StreamWriter.awrite(): spooled partial %d bytes", res) 179 | assert res < sz 180 | off += res 181 | sz -= res 182 | yield IOWrite(self.s) 183 | #assert s2.fileno() == self.s.fileno() 184 | if DEBUG and __debug__: 185 | log.debug("StreamWriter.awrite(): can write more") 186 | 187 | # Write piecewise content from iterable (usually, a generator) 188 | def awriteiter(self, iterable): 189 | for buf in iterable: 190 | yield from self.awrite(buf) 191 | 192 | def aclose(self): 193 | yield IOWriteDone(self.s) 194 | self.s.close() 195 | 196 | def get_extra_info(self, name, default=None): 197 | return self.extra.get(name, default) 198 | 199 | def __repr__(self): 200 | return "" % self.s 201 | 202 | 203 | def open_connection(host, port, ssl=False): 204 | if DEBUG and __debug__: 205 | log.debug("open_connection(%s, %s)", host, port) 206 | ai = _socket.getaddrinfo(host, port, 0, _socket.SOCK_STREAM) 207 | ai = ai[0] 208 | s = _socket.socket(ai[0], ai[1], ai[2]) 209 | s.setblocking(False) 210 | try: 211 | s.connect(ai[-1]) 212 | except OSError as e: 213 | if e.args[0] != uerrno.EINPROGRESS: 214 | raise 215 | if DEBUG and __debug__: 216 | log.debug("open_connection: After connect") 217 | yield IOWrite(s) 218 | # if __debug__: 219 | # assert s2.fileno() == s.fileno() 220 | if DEBUG and __debug__: 221 | log.debug("open_connection: After iowait: %s", s) 222 | if ssl: 223 | print("Warning: uasyncio SSL support is alpha") 224 | import ussl 225 | s.setblocking(True) 226 | s2 = ussl.wrap_socket(s) 227 | s.setblocking(False) 228 | return StreamReader(s, s2), StreamWriter(s2, {}) 229 | return StreamReader(s), StreamWriter(s, {}) 230 | 231 | 232 | def start_server(client_coro, host, port, backlog=10): 233 | if DEBUG and __debug__: 234 | log.debug("start_server(%s, %s)", host, port) 235 | ai = _socket.getaddrinfo(host, port, 0, _socket.SOCK_STREAM) 236 | ai = ai[0] 237 | s = _socket.socket(ai[0], ai[1], ai[2]) 238 | s.setblocking(False) 239 | 240 | s.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR, 1) 241 | s.bind(ai[-1]) 242 | s.listen(backlog) 243 | while True: 244 | if DEBUG and __debug__: 245 | log.debug("start_server: Before accept") 246 | yield IORead(s) 247 | if DEBUG and __debug__: 248 | log.debug("start_server: After iowait") 249 | s2, client_addr = s.accept() 250 | s2.setblocking(False) 251 | if DEBUG and __debug__: 252 | log.debug("start_server: After accept: %s", s2) 253 | extra = {"peername": client_addr} 254 | yield client_coro(StreamReader(s2), StreamWriter(s2, extra)) 255 | 256 | 257 | import uasyncio.core 258 | uasyncio.core._event_loop_class = PollEventLoop 259 | -------------------------------------------------------------------------------- /uasyncio/core.py: -------------------------------------------------------------------------------- 1 | import utime as time 2 | import utimeq 3 | import ucollections 4 | 5 | 6 | type_gen = type((lambda: (yield))()) 7 | 8 | DEBUG = 0 9 | log = None 10 | 11 | def set_debug(val): 12 | global DEBUG, log 13 | DEBUG = val 14 | if val: 15 | import logging 16 | log = logging.getLogger("uasyncio.core") 17 | 18 | 19 | class CancelledError(Exception): 20 | pass 21 | 22 | 23 | class TimeoutError(CancelledError): 24 | pass 25 | 26 | 27 | class EventLoop: 28 | 29 | def __init__(self, runq_len=16, waitq_len=16): 30 | self.runq = ucollections.deque((), runq_len, True) 31 | self.waitq = utimeq.utimeq(waitq_len) 32 | # Current task being run. Task is a top-level coroutine scheduled 33 | # in the event loop (sub-coroutines executed transparently by 34 | # yield from/await, event loop "doesn't see" them). 35 | self.cur_task = None 36 | 37 | def time(self): 38 | return time.ticks_ms() 39 | 40 | def create_task(self, coro): 41 | # CPython 3.4.2 42 | self.call_later_ms(0, coro) 43 | # CPython asyncio incompatibility: we don't return Task object 44 | 45 | def call_soon(self, callback, *args): 46 | if __debug__ and DEBUG: 47 | log.debug("Scheduling in runq: %s", (callback, args)) 48 | self.runq.append(callback) 49 | if not isinstance(callback, type_gen): 50 | self.runq.append(args) 51 | 52 | def call_later(self, delay, callback, *args): 53 | self.call_at_(time.ticks_add(self.time(), int(delay * 1000)), callback, args) 54 | 55 | def call_later_ms(self, delay, callback, *args): 56 | if not delay: 57 | return self.call_soon(callback, *args) 58 | self.call_at_(time.ticks_add(self.time(), delay), callback, args) 59 | 60 | def call_at_(self, time, callback, args=()): 61 | if __debug__ and DEBUG: 62 | log.debug("Scheduling in waitq: %s", (time, callback, args)) 63 | self.waitq.push(time, callback, args) 64 | 65 | def wait(self, delay): 66 | # Default wait implementation, to be overriden in subclasses 67 | # with IO scheduling 68 | if __debug__ and DEBUG: 69 | log.debug("Sleeping for: %s", delay) 70 | time.sleep_ms(delay) 71 | 72 | def run_forever(self): 73 | cur_task = [0, 0, 0] 74 | while True: 75 | # Expire entries in waitq and move them to runq 76 | tnow = self.time() 77 | while self.waitq: 78 | t = self.waitq.peektime() 79 | delay = time.ticks_diff(t, tnow) 80 | if delay > 0: 81 | break 82 | self.waitq.pop(cur_task) 83 | if __debug__ and DEBUG: 84 | log.debug("Moving from waitq to runq: %s", cur_task[1]) 85 | self.call_soon(cur_task[1], *cur_task[2]) 86 | 87 | # Process runq 88 | l = len(self.runq) 89 | if __debug__ and DEBUG: 90 | log.debug("Entries in runq: %d", l) 91 | while l: 92 | cb = self.runq.popleft() 93 | l -= 1 94 | args = () 95 | if not isinstance(cb, type_gen): 96 | args = self.runq.popleft() 97 | l -= 1 98 | if __debug__ and DEBUG: 99 | log.info("Next callback to run: %s", (cb, args)) 100 | cb(*args) 101 | continue 102 | 103 | if __debug__ and DEBUG: 104 | log.info("Next coroutine to run: %s", (cb, args)) 105 | self.cur_task = cb 106 | delay = 0 107 | try: 108 | if args is (): 109 | ret = next(cb) 110 | else: 111 | ret = cb.send(*args) 112 | if __debug__ and DEBUG: 113 | log.info("Coroutine %s yield result: %s", cb, ret) 114 | if isinstance(ret, SysCall1): 115 | arg = ret.arg 116 | if isinstance(ret, SleepMs): 117 | delay = arg 118 | elif isinstance(ret, IORead): 119 | cb.pend_throw(False) 120 | self.add_reader(arg, cb) 121 | continue 122 | elif isinstance(ret, IOWrite): 123 | cb.pend_throw(False) 124 | self.add_writer(arg, cb) 125 | continue 126 | elif isinstance(ret, IOReadDone): 127 | self.remove_reader(arg) 128 | elif isinstance(ret, IOWriteDone): 129 | self.remove_writer(arg) 130 | elif isinstance(ret, StopLoop): 131 | return arg 132 | else: 133 | assert False, "Unknown syscall yielded: %r (of type %r)" % (ret, type(ret)) 134 | elif isinstance(ret, type_gen): 135 | self.call_soon(ret) 136 | elif isinstance(ret, int): 137 | # Delay 138 | delay = ret 139 | elif ret is None: 140 | # Just reschedule 141 | pass 142 | elif ret is False: 143 | # Don't reschedule 144 | continue 145 | else: 146 | assert False, "Unsupported coroutine yield value: %r (of type %r)" % (ret, type(ret)) 147 | except StopIteration as e: 148 | if __debug__ and DEBUG: 149 | log.debug("Coroutine finished: %s", cb) 150 | continue 151 | except CancelledError as e: 152 | if __debug__ and DEBUG: 153 | log.debug("Coroutine cancelled: %s", cb) 154 | continue 155 | # Currently all syscalls don't return anything, so we don't 156 | # need to feed anything to the next invocation of coroutine. 157 | # If that changes, need to pass that value below. 158 | if delay: 159 | self.call_later_ms(delay, cb) 160 | else: 161 | self.call_soon(cb) 162 | 163 | # Wait until next waitq task or I/O availability 164 | delay = 0 165 | if not self.runq: 166 | delay = -1 167 | if self.waitq: 168 | tnow = self.time() 169 | t = self.waitq.peektime() 170 | delay = time.ticks_diff(t, tnow) 171 | if delay < 0: 172 | delay = 0 173 | self.wait(delay) 174 | 175 | def run_until_complete(self, coro): 176 | def _run_and_stop(): 177 | yield from coro 178 | yield StopLoop(0) 179 | self.call_soon(_run_and_stop()) 180 | self.run_forever() 181 | 182 | def stop(self): 183 | self.call_soon((lambda: (yield StopLoop(0)))()) 184 | 185 | def close(self): 186 | pass 187 | 188 | 189 | class SysCall: 190 | 191 | def __init__(self, *args): 192 | self.args = args 193 | 194 | def handle(self): 195 | raise NotImplementedError 196 | 197 | # Optimized syscall with 1 arg 198 | class SysCall1(SysCall): 199 | 200 | def __init__(self, arg): 201 | self.arg = arg 202 | 203 | class StopLoop(SysCall1): 204 | pass 205 | 206 | class IORead(SysCall1): 207 | pass 208 | 209 | class IOWrite(SysCall1): 210 | pass 211 | 212 | class IOReadDone(SysCall1): 213 | pass 214 | 215 | class IOWriteDone(SysCall1): 216 | pass 217 | 218 | 219 | _event_loop = None 220 | _event_loop_class = EventLoop 221 | def get_event_loop(runq_len=16, waitq_len=16): 222 | global _event_loop 223 | if _event_loop is None: 224 | _event_loop = _event_loop_class(runq_len, waitq_len) 225 | return _event_loop 226 | 227 | def sleep(secs): 228 | yield int(secs * 1000) 229 | 230 | # Implementation of sleep_ms awaitable with zero heap memory usage 231 | class SleepMs(SysCall1): 232 | 233 | def __init__(self): 234 | self.v = None 235 | self.arg = None 236 | 237 | def __call__(self, arg): 238 | self.v = arg 239 | #print("__call__") 240 | return self 241 | 242 | def __iter__(self): 243 | #print("__iter__") 244 | return self 245 | 246 | def __next__(self): 247 | if self.v is not None: 248 | #print("__next__ syscall enter") 249 | self.arg = self.v 250 | self.v = None 251 | return self 252 | #print("__next__ syscall exit") 253 | _stop_iter.__traceback__ = None 254 | raise _stop_iter 255 | 256 | _stop_iter = StopIteration() 257 | sleep_ms = SleepMs() 258 | 259 | 260 | def cancel(coro): 261 | prev = coro.pend_throw(CancelledError()) 262 | if prev is False: 263 | _event_loop.call_soon(coro) 264 | 265 | 266 | class TimeoutObj: 267 | def __init__(self, coro): 268 | self.coro = coro 269 | 270 | 271 | def wait_for_ms(coro, timeout): 272 | 273 | def waiter(coro, timeout_obj): 274 | res = yield from coro 275 | if __debug__ and DEBUG: 276 | log.debug("waiter: cancelling %s", timeout_obj) 277 | timeout_obj.coro = None 278 | return res 279 | 280 | def timeout_func(timeout_obj): 281 | if timeout_obj.coro: 282 | if __debug__ and DEBUG: 283 | log.debug("timeout_func: cancelling %s", timeout_obj.coro) 284 | prev = timeout_obj.coro.pend_throw(TimeoutError()) 285 | #print("prev pend", prev) 286 | if prev is False: 287 | _event_loop.call_soon(timeout_obj.coro) 288 | 289 | timeout_obj = TimeoutObj(_event_loop.cur_task) 290 | _event_loop.call_later_ms(timeout, timeout_func, timeout_obj) 291 | return (yield from waiter(coro, timeout_obj)) 292 | 293 | 294 | def wait_for(coro, timeout): 295 | return wait_for_ms(coro, int(timeout * 1000)) 296 | 297 | 298 | def coroutine(f): 299 | return f 300 | 301 | # 302 | # The functions below are deprecated in uasyncio, and provided only 303 | # for compatibility with CPython asyncio 304 | # 305 | 306 | def ensure_future(coro, loop=_event_loop): 307 | _event_loop.call_soon(coro) 308 | # CPython asyncio incompatibility: we don't return Task object 309 | return coro 310 | 311 | 312 | # CPython asyncio incompatibility: Task is a function, not a class (for efficiency) 313 | def Task(coro, loop=_event_loop): 314 | # Same as async() 315 | _event_loop.call_soon(coro) 316 | --------------------------------------------------------------------------------