def uart0_handler(uart_o): global uart0_irq global uart0_int_count if uart0_irq.flags() & UART.RX_ANY: uart0_int_count += 1 def uart1_handler(uart_o): global uart1_irq global uart1_int_count if uart1_irq.flags() & UART.RX_ANY: uart1_int_count += 1 uart0_irq = uart0.irq(trigger=UART.RX_ANY, handler=uart0_handler) uart1_irq = uart1.irq(trigger=UART.RX_ANY, handler=uart1_handler) uart0.write(b"123") # wait for the characters to be received while not uart1.any(): pass time.sleep_us(100) print(uart1.any() == 3) print(uart1_int_count > 0) print(uart1_irq.flags() == 0) print(uart0_irq.flags() == 0) print(uart1.read() == b"123") uart1.write(b"12345")
class Esp8266(object): ''' WiFi module TODO: 20200326 DPM: This class is intended for having a single client. Another limitation is that any request to the module should not be performed meanwhile it is in server mode. Further improvements must be done to avoid these limitations. A possible solution could be capturing all messages from the module in a IRQ-handler where the contents could be processed to rise events or send data through queues or even change the status of the object. ''' BYTES_ENCODING = "ascii" OP_MODE_CLIENT = const(1) OP_MODE_AP = const(2) SECURITY_OPEN = const(0) SECURITY_WEP = const(1) SECURITY_WPA = const(2) SECURITY_WPA2 = const(3) SECURITY_WPA_WPA2 = const(4) def __init__(self, uartId, enablePin=None, readTimeout=1000, baud=115200, debug=False): ''' Constructor @param uartId: Id of the UART port. @param enablePin: (default=None) Pin to handle the enable signal of the ESP8266 module. If not provided here, this line should be provided using a different way to the module in order to activate it. @param readTimeout: Time to wait for any incoming data in milliseconds (default 1000) @param baud: (default=115200) bit-rate of the communication @param debug: (default=False) prints debug information on the REPL ''' self._uart = UART(uartId, baud) if enablePin != None: self._enablePin = Pin(enablePin, Pin.OUT) else: self._enablePin = None self._readTimeout = readTimeout self._debug = debug # Reset module self.disable() sleep_ms(500) def start(self, txPower=10): ''' Starts the module. Enable the module and set the TX-power. @param txPower: (default=10) the tx-power ''' self.enable() self.setTxPower(txPower) def cleanup(self): self.disable() self._uart.deinit() def enable(self): ''' Enables the module, if enable pin was provided ''' if self._enablePin != None: self._enablePin.on() sleep_ms(500) if self._uart.any(): self._flushRx() sleep_ms(500) def disable(self): ''' Disables the module, if enable pin was provided ''' if self._enablePin != None: self._enablePin.off() def isPresent(self): ''' Checks if the module is present. Just sends a "AT" command. @return: True if it's present ''' self._write("AT") return self._isOk() def reset(self, txPower=10): ''' Resets the module @param txPower: (default=10) The tx-power ''' self._write("AT+RST") sleep_ms(300) assert self._isOk() self.setTxPower(txPower) def getVersion(self): ''' Gets the firmware version @return: array with the version information as follows: [AT version, SDK version, compile time] ''' version = [] self._write("AT+GMR") data = self._readline() while data != None and not data.startswith( bytes("OK", Esp8266.BYTES_ENCODING)): if not data.startswith(bytes("AT+GMR", Esp8266.BYTES_ENCODING)): version.append(data.strip()) data = self._readline() return version def getOperatingMode(self): ''' @return: The operating mode @raise Exception: On non reading mode ''' self._write("AT+CWMODE?") data = self._readline() while data != None and not data.startswith( bytes("+CWMODE", Esp8266.BYTES_ENCODING)): data = self._readline() if data != None: mode = int( data.split(bytes(":", Esp8266.BYTES_ENCODING))[1].strip()) self._flushRx() else: raise Exception("Cannot read operating mode.") return mode def setOperatingMode(self, mode): ''' @param mode: The operating mode. Modes can be added using the or-operator. ''' self._write("AT+CWMODE={0}".format(mode)) assert self._isOk() def join(self, ssid, passwd=None): ''' Joins to a wireless network. Only when client mode is enabled. @param ssid: Network name @param passwd: Password (Optional for open networks). Max 64 bytes ASCII @raise Exception: On join error. Error codes: 1: connection timeout. 2: wrong password. 3: cannot find the target AP. 4: connection failed. ''' if passwd != None and passwd != "": self._write("AT+CWJAP_CUR=\"{0}\",\"{1}\"".format(ssid, passwd)) else: self._write("AT+CWJAP_CUR=\"{0}\"".format(ssid)) data = self._readline() or "" isError = data.startswith(bytes("FAIL", Esp8266.BYTES_ENCODING)) while not isError and not data.startswith( bytes("OK", Esp8266.BYTES_ENCODING)): sleep_ms(100) data = self._readline() or "" isError = data.startswith(bytes("FAIL", Esp8266.BYTES_ENCODING)) self._flushRx() if isError: raise Exception("Error: Cannot join to network.") def joinedNetwork(self): ''' Gets the name of the joined network where the module is currently connected to, if any. @return: Name of the network ''' name = "" self._write("AT+CWJAP?") data = self._readline() self._flushRx() if data.startswith(bytes("+CWJAP", Esp8266.BYTES_ENCODING)): networkData = data.split(bytes(":", Esp8266.BYTES_ENCODING))[1] name = networkData.split(bytes(",", Esp8266.BYTES_ENCODING))[0] return name def discover(self): ''' List available discovered networks. This list may not be exhaustive. @return: List of networks ''' pass def disconnect(self): ''' Disconnects the current network. Only when it is already joined. ''' pass def setAccessPointConfig(self, ssid, passwd=None, channel=1, security=SECURITY_OPEN): ''' Sets up the AP configuration. @param ssid: Name of the network @param passwd: Password (not used for open AP) @param channel: WiFi channel (default 1) @param security: Security mode, like WEP, WPA or WPA2 (default open) ''' if security != Esp8266.SECURITY_OPEN: self._write("AT+CWSAP=\"{0}\",\"{1}\",{2},{3}".format( ssid, passwd, channel, security)) else: self._write("AT+CWSAP=\"{0}\",\"\",{1},{2}".format( ssid, channel, security)) assert self._isOk() def getAccessPointConfig(self): ''' Queries the current access point configuration. This works when AP mode is enabled. ''' pass def getAddresses(self): ''' Gets the addresses and MACs of the module depending of the operating mode. It returns addresses for the currently active modes. Possible address-types are: APIP, APMAC, STAIP, STAMAC @return: Dictionary of addresses and MACs with the format {address-type, address} ''' addresses = {} self._write("AT+CIFSR") data = self._readline() #TODO: It seems not reading the contents while data != None and data.startswith( bytes("+CIFSR", Esp8266.BYTES_ENCODING)): addressInfo = data.strip().split(bytes( ":", Esp8266.BYTES_ENCODING))[1].split( bytes(",", Esp8266.BYTES_ENCODING)) addresses[addressInfo[0]] = addressInfo[1] return addresses def getAddressByType(self, addrType): ''' Get the module address of a concrete type, if enabled. @param addrType: Possible address-types are: APIP, APMAC, STAIP, STAMAC @return: The address or none if the type is not enabled ''' address = b"" addresses = self.getAddresses() apIpKey = bytes(addrType, Esp8266.BYTES_ENCODING) if apIpKey in addresses.keys(): address = addresses[apIpKey] return address def getApIpAddress(self): ''' @return: The IP address of the module's access point or empty if not enabled. ''' return self.getAddressByType("APIP") def setApIpAddress(self, ip, gateway, netmask="255.255.255.0"): ''' Sets the IP address of the module's access point @param ip: IP address @param gateway: Network's gateway @param netmask: (default="255.255.255.0") Network's mask of the IP addresses ''' self._write("AT+CIPAP_CUR=\"{0}\",\"{1}\",\"{2}\"".format( ip, gateway, netmask)) assert self._isOk() def getStaIpAddress(self): ''' @return: The IP address when it is connected to a station or empty if not enabled ''' return self.getAddressByType("STAIP") def setStaIpAddress(self, ip, gateway, netmask="255.255.255.0"): ''' Sets the IP address when it is connected to a station @param ip: IP address @param gateway: Network's gateway @param netmask: (default="255.255.255.0") Network's mask for the IP addresses ''' self._write("AT+CIPSTA_CUR=\"{0}\",\"{1}\",\"{2}\"".format( ip, gateway, netmask)) #This command is returning b"O" instead of b"OK\r\n" assert self._startsLineWith("O") def initServer(self, connectionClass, extraObj=None, port=333): ''' Initializes the socket server. 20200325 DPM: There is a limitation of the length of the received messages: It can receive up to 53 bytes at once and therefore further bytes will be missed. In instance, the client could send: '123456789012345678901234567890123456789012345678901234567890' (70 bytes) But it receives: b'+IPD,0,70:12345678901234567890123456789012345678901234567890123' The strange thing is, it knows that it should be 70 bytes, but it misses the last 17 bytes, which won't came in any further input either. @see: Connection class @param connectionClass: (instance of Connection class) Class which implements connection actions @param extraObj: (default=None) Extra object to pass to the ConnectionClass new instances @param port: (default=333) Listening port ''' self._write("AT+CIPMUX=1") assert self._isOk() self._write("AT+CIPSERVER=1,{0}".format(port)) assert self._isOk() #TODO: 20200326 DPM: Enabling IRQ here makes any request to the module not working properly. self._enableRxIrq() self._connections = {} self._connectionClass = connectionClass self._extraObj = extraObj def stopServer(self): ''' Stops the socket server. ''' self._disableRxIrq() self._write("AT+CIPSERVER=0") assert self._isOk() def _send(self, clientId, data): #TODO: 20200326 DPM: Disabling the IRQ during the send will miss any new client connection. # Therefore a single client can be used safely for the moment. #TODO: 20200326 DPM: Implement with a lock to avoid different coroutines sending at the same time. # Otherwise the concurrent send requests could be messed. self._disableRxIrq() self._write("AT+CIPSEND={0},{1}".format(clientId, len(data))) self._flushRx() self._write(data) line = self._readline() while line != None and not line.startswith("SEND"): line = self._readline() self._enableRxIrq() assert line != None and "OK" in line def getTimeout(self): ''' Gets the socket client disconnect timeout (AT+CIPSTO?) @return: The timeout as seconds ''' pass def setTimeout(self, timeout): ''' Sets the socket client disconnect timeout (AT+CIPSTO=nn) @param timeout: The timeout as seconds ''' pass def setTxPower(self, value): ''' Sets the RF power for transmission @param value: [0..82]; unit 0.25 dBm ''' self._write("AT+RFPOWER={0}".format(value)) assert self._isOk() def _write(self, data): if self._debug: print("=> {0}".format(bytes(data, Esp8266.BYTES_ENCODING))) self._uart.write("{0}\r\n".format(data)) sleep_ms(200) def _readline(self): startTicks = ticks_ms() while self._uart.any() == 0 and ticks_diff( ticks_ms(), startTicks) < self._readTimeout: sleep_ms(50) data = None if self._uart.any() != 0: data = self._uart.readline() if self._debug: if data: print("<= {0}".format(data)) else: print("UART timeout") return data def _flushRx(self): ''' Flushes the RX-buffer ''' if self._uart.any(): buff = self._uart.read() if self._debug: print("(flushed)<= {0}".format(buff)) def _startsLineWith(self, text, flush=True): ''' Checks whether the next line starts with a text @param text: Text to find @param flush: Flushes the RX-buffer after checking @return: True if the line starts with the text ''' data = self._readline() startsWith = data != None and data.startswith( bytes(text, Esp8266.BYTES_ENCODING)) while data != None and not startsWith: data = self._readline() startsWith = data != None and data.startswith( bytes(text, Esp8266.BYTES_ENCODING)) if flush: self._flushRx() return startsWith def _isOk(self, flush=True): ''' @param flush: Flushes the RX-buffer after checking @return: True if OK return was found ''' return self._startsLineWith("OK", flush) def _isError(self, flush=True): ''' @param flush: Flushes the RX-buffer after checking @return: True if Error return was found ''' return self._startsLineWith("ERROR", flush) def _enableRxIrq(self): self._uart.irq(trigger=UART.IRQ_RXIDLE, handler=self._onDataReceived) def _disableRxIrq(self): self._uart.irq(trigger=UART.IRQ_RXIDLE, handler=None) def _onDataReceived(self, _): line = None while self._uart.any(): line = self._readline() if line.startswith(b"+IPD"): #Message from client contents = line.split(b":", 1) data = contents[0].split(b",") clientId = int(data[1]) message = str(contents[1][:int(data[2])], Esp8266.BYTES_ENCODING) get_event_loop().create_task( self._connections[clientId].onReceived(message)) else: line = line.strip() contents = line.split(b",", 1) if len(contents) > 1: if contents[1] == b"CONNECT": #New client clientId = int(contents[0]) self._connections[clientId] = self._connectionClass( clientId, self, self._extraObj) get_event_loop().create_task( self._connections[clientId].onConnected()) elif contents[1] == b"CLOSED": #Client gone clientId = int(contents[0]) self._connections[clientId].onClose() del self._connections[clientId]