|
| 1 | +"""A 'socket' compatible interface thru the ESP SPI command set""" |
| 2 | +from micropython import const |
| 3 | + |
| 4 | +_the_interface = None # pylint: disable=invalid-name |
| 5 | +def set_interface(iface): |
| 6 | + """Helper to set the global internet interface""" |
| 7 | + global _the_interface # pylint: disable=global-statement, invalid-name |
| 8 | + _the_interface = iface |
| 9 | + |
| 10 | +SOCK_STREAM = const(1) |
| 11 | +AF_INET = const(2) |
| 12 | + |
| 13 | +# pylint: disable=too-many-arguments, unused-argument |
| 14 | +def getaddrinfo(host, port, family=0, socktype=0, proto=0, flags=0): |
| 15 | + """Given a hostname and a port name, return a 'socket.getaddrinfo' |
| 16 | + compatible list of tuples. Honestly, we ignore anything but host & port""" |
| 17 | + if not isinstance(port, int): |
| 18 | + raise RuntimeError("Port must be an integer") |
| 19 | + ipaddr = _the_interface.get_host_by_name(host) |
| 20 | + return [(AF_INET, socktype, proto, '', (ipaddr, port))] |
| 21 | +# pylint: enable=too-many-arguments, unused-argument |
| 22 | + |
| 23 | +# pylint: disable=unused-argument, redefined-builtin, invalid-name |
| 24 | +class socket: |
| 25 | + """A simplified implementation of the Python 'socket' class, for connecting |
| 26 | + through an interface to a remote device""" |
| 27 | + def __init__(self, family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None): |
| 28 | + if family != AF_INET: |
| 29 | + raise RuntimeError("Only AF_INET family supported") |
| 30 | + if type != SOCK_STREAM: |
| 31 | + raise RuntimeError("Only SOCK_STREAM type supported") |
| 32 | + self._buffer = b'' |
| 33 | + self._socknum = _the_interface.get_socket() |
| 34 | + |
| 35 | + def connect(self, address, conntype=None): |
| 36 | + """Connect the socket to the 'address' (which can be 32bit packed IP or |
| 37 | + a hostname string). 'conntype' is an extra that may indicate SSL or not, |
| 38 | + depending on the underlying interface""" |
| 39 | + host, port = address |
| 40 | + if not _the_interface.socket_connect(self._socknum, host, port): |
| 41 | + raise RuntimeError("Failed to connect to host", host) |
| 42 | + self._buffer = b'' |
| 43 | + |
| 44 | + def write(self, data): # pylint: disable=no-self-use |
| 45 | + """Send some data to the socket""" |
| 46 | + _the_interface.socket_write(self._socknum, data) |
| 47 | + |
| 48 | + def readline(self): |
| 49 | + """Attempt to return as many bytes as we can up to but not including '\r\n'""" |
| 50 | + while b'\r\n' not in self._buffer: |
| 51 | + # there's no line already in there, read some more |
| 52 | + avail = _the_interface.socket_available(self._socknum) |
| 53 | + if avail: |
| 54 | + self._buffer += _the_interface.socket_read(self._socknum, avail) |
| 55 | + firstline, self._buffer = self._buffer.split(b'\r\n', 1) |
| 56 | + return firstline |
| 57 | + |
| 58 | + def read(self, size=0): |
| 59 | + """Read up to 'size' bytes from the socket, this may be buffered internally! |
| 60 | + If 'size' isnt specified, return everything in the buffer.""" |
| 61 | + avail = _the_interface.socket_available(self._socknum) |
| 62 | + if avail: |
| 63 | + self._buffer += _the_interface.socket_read(self._socknum, avail) |
| 64 | + if size == 0: # read as much as we can |
| 65 | + ret = self._buffer |
| 66 | + self._buffer = b'' |
| 67 | + return ret |
| 68 | + while len(self._buffer) < size: |
| 69 | + avail = _the_interface.socket_available(self._socknum) |
| 70 | + if avail: |
| 71 | + self._buffer += _the_interface.socket_read(self._socknum, avail) |
| 72 | + ret = self._buffer[:size] |
| 73 | + self._buffer = self._buffer[size:] |
| 74 | + return ret |
| 75 | + |
| 76 | + def close(self): |
| 77 | + """Close the socket, after reading whatever remains""" |
| 78 | + _the_interface.socket_close(self._socknum) |
| 79 | +# pylint: enable=unused-argument, redefined-builtin, invalid-name |
0 commit comments