ESP32-S2 MQTT subscriber "OSError: [Errno 113] ECONNABORTED"

All ESP32 boards running MicroPython.
Target audience: MicroPython users with an ESP32 board.
ash10
Posts: 11
Joined: Tue Mar 08, 2022 1:43 pm

ESP32-S2 MQTT subscriber "OSError: [Errno 113] ECONNABORTED"

Post by ash10 » Wed Mar 09, 2022 12:15 pm

Hi,

I am having an mqtt problem and I am hoping that someone can help.

I am trying to subscribe to a topic on a public broker (test.mosquitto.org).

When I run the code on an ESP-WROOM-32 dev board, it works.
I verified this by publishing to the broker on an arbitrary topic "asdf/asdf/123" using MQTTX as a client.
The messages comes through and are displayed in the terminal output for the dev board.

But when I run the same code on an ESP32-S2-MINI-1U-N4 module, it gives me

"OSError: [Errno 113] ECONNABORTED"

and fails to connect to the broker.

The ESP32-S2-MINI-1U-N4 is on a custom piece of hardware flashed with MicroPython v1.18 (the generic ESP32-S2 version).

I'm using the mqtt_as library.

This is the code that I am using, its from the example usage section of the mqtt_as documentation, along with the relevant changes:

Code: Select all

import time
time.sleep(5)
from mqtt.mqtt_as import MQTTClient, config
import uasyncio as asyncio

SERVER = 'test.mosquitto.org'


def callback(topic, msg, retained):
    print((topic, msg, retained))

async def conn_han(client):
    await client.subscribe('asdf/asdf/123', 0)
    
async def main(client):
    await client.connect()
    n = 0
    while True:
        await asyncio.sleep(5)
        print('publish', n)
        # If WiFi is down the following will pause for the duration.
        await client.publish('result', '{}'.format(n), qos = 0)
        n += 1

config['subs_cb'] = callback
config['connect_coro'] = conn_han
config['server'] = SERVER

config['ssid'] = 'my_ssid'                     
config['wifi_pw'] = 'my_password'   

MQTTClient.DEBUG = True  # Optional: print diagnostic messages
client = MQTTClient(config)
try:
    asyncio.run(main(client))
finally:
    client.close()  # Prevent LmacRxBlk:1 errors

This is the error output:

Code: Select all

>>> %Run -c $EDITOR_CONTENT
Checking WiFi integrity.
Got reliable connection
Connecting to broker.
Traceback (most recent call last):
  File "<stdin>", line 35, in <module>
  File "uasyncio/core.py", line 1, in run
  File "uasyncio/core.py", line 1, in run_until_complete
  File "uasyncio/core.py", line 1, in run_until_complete
  File "<stdin>", line 16, in main
  File "mqtt/mqtt_as.py", line 523, in connect
  File "mqtt/mqtt_as.py", line 249, in _connect
  File "mqtt/mqtt_as.py", line 188, in _as_write
OSError: [Errno 113] ECONNABORTED
Any help will be much appreciated.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: MQTT subscriber "OSError: [Errno 113] ECONNABORTED"

Post by pythoncoder » Fri Mar 11, 2022 10:05 am

This library has only been tested on ESP32, ESP8266 and Pyboard D. I can't think of a reason why it's failing on ESP32-S2. I have no hardware to investigate this and have no experience with the S2 so I'm not sure how to help here.

I assume that in other respects it can maintain a wifi link successfully. It is behaving as if the server were dropping the connection as soon as communication has begun. This makes no sense assuming that your chip can maintain a wifi link normally. The only thing I can think of is a firmware issue with nonblocking sockets.

Perhaps another S2 user might have some ideas.
Peter Hinch
Index to my micropython libraries.

ash10
Posts: 11
Joined: Tue Mar 08, 2022 1:43 pm

Re: ESP32-S2 MQTT subscriber "OSError: [Errno 113] ECONNABORTED"

Post by ash10 » Fri Mar 11, 2022 11:52 am

It does maintain WiFi links with no problems so far, that being said I haven't really done much besides pinging google to check that I have internet access (which I do).

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: ESP32-S2 MQTT subscriber "OSError: [Errno 113] ECONNABORTED"

Post by pythoncoder » Sat Mar 12, 2022 12:57 pm

I'm puzzled by this. I've ordered an S2 and will report back.

One option would be to try the official mqtt-simple library to see if that can reliably connect with your network and hardware.
Peter Hinch
Index to my micropython libraries.

ash10
Posts: 11
Joined: Tue Mar 08, 2022 1:43 pm

Re: ESP32-S2 MQTT subscriber "OSError: [Errno 113] ECONNABORTED"

Post by ash10 » Mon Mar 14, 2022 9:18 am

Thanks for the effort.

So I tried your suggestion, I used the official mqtt-simple library and made the appropriate changes to the "example_pub.py" file.
I published to the topic using the WROOM dev board and it worked fine.

Code: Select all

from mqtt.simple import MQTTClient
import time

# Test reception e.g. with:
# mosquitto_sub -t foo_topic

def do_connect():
    import network
    sta_if = network.WLAN(network.STA_IF)
    if not sta_if.isconnected():
        print('connecting to network...')
        sta_if.active(True)
        sta_if.connect('my_ssid', 'my_password') 
        while not sta_if.isconnected():
            print('wifi connecting...')
            pass
    print('network config:', sta_if.ifconfig())
    return True

def main(server="test.mosquitto.org"):
    if do_connect():
        print('WiFi connected...')
        time.sleep(1)
    c = MQTTClient("umqtt_client", server, keepalive=60)
    c.connect()
    c.publish(b"asdf/asdf/123", b"hello_esp_S2")
    c.disconnect()


if __name__ == "__main__":
    main()
So I tried the same code on the S2 and I get the same "OSError: [Errno 113] ECONNABORTED" on connect.

Code: Select all

>>> %Run -c $EDITOR_CONTENT
network config: ('192.168.0.148', '255.255.255.0', '192.168.0.1', '192.168.0.1')
WiFi connected...
bytearray(b'\x04MQTT\x04\x02\x00<')
Traceback (most recent call last):
  File "<stdin>", line 31, in <module>
  File "<stdin>", line 25, in main
  File "mqtt/simple.py", line 99, in connect
OSError: [Errno 113] ECONNABORTED
It fails on "self.sock.write(msg)", so I'm guessing that it could be that something is causing a change in the byte array used to establish connection to the broker. Just a guess though, as I have never looked at the protocol down to this level.

Code: Select all

import usocket as socket
import ustruct as struct
from ubinascii import hexlify


class MQTTException(Exception):
    pass


class MQTTClient:
    def __init__(
        self,
        client_id,
        server,
        port=0,
        user=None,
        password=None,
        keepalive=0,
        ssl=False,
        ssl_params={},
    ):
        if port == 0:
            port = 8883 if ssl else 1883
        self.client_id = client_id
        self.sock = None
        self.server = server
        self.port = port
        self.ssl = ssl
        self.ssl_params = ssl_params
        self.pid = 0
        self.cb = None
        self.user = user
        self.pswd = password
        self.keepalive = keepalive
        self.lw_topic = None
        self.lw_msg = None
        self.lw_qos = 0
        self.lw_retain = False

    def _send_str(self, s):
        self.sock.write(struct.pack("!H", len(s)))
        self.sock.write(s)

    def _recv_len(self):
        n = 0
        sh = 0
        while 1:
            b = self.sock.read(1)[0]
            n |= (b & 0x7F) << sh
            if not b & 0x80:
                return n
            sh += 7

    def set_callback(self, f):
        self.cb = f

    def set_last_will(self, topic, msg, retain=False, qos=0):
        assert 0 <= qos <= 2
        assert topic
        self.lw_topic = topic
        self.lw_msg = msg
        self.lw_qos = qos
        self.lw_retain = retain

    def connect(self, clean_session=True):
        self.sock = socket.socket()
        addr = socket.getaddrinfo(self.server, self.port)[0][-1]
        self.sock.connect(addr)
        if self.ssl:
            import ussl

            self.sock = ussl.wrap_socket(self.sock, **self.ssl_params)
        premsg = bytearray(b"\x10\0\0\0\0\0")
        msg = bytearray(b"\x04MQTT\x04\x02\0\0")

        sz = 10 + 2 + len(self.client_id)
        msg[6] = clean_session << 1
        if self.user is not None:
            sz += 2 + len(self.user) + 2 + len(self.pswd)
            msg[6] |= 0xC0
        if self.keepalive:
            assert self.keepalive < 65536
            msg[7] |= self.keepalive >> 8
            msg[8] |= self.keepalive & 0x00FF
        if self.lw_topic:
            sz += 2 + len(self.lw_topic) + 2 + len(self.lw_msg)
            msg[6] |= 0x4 | (self.lw_qos & 0x1) << 3 | (self.lw_qos & 0x2) << 3
            msg[6] |= self.lw_retain << 5

        i = 1
        while sz > 0x7F:
            premsg[i] = (sz & 0x7F) | 0x80
            sz >>= 7
            i += 1
        premsg[i] = sz

        self.sock.write(premsg, i + 2)
        print(msg)
        self.sock.write(msg)                                     <-- Line 99
        # print(hex(len(msg)), hexlify(msg, ":"))
        self._send_str(self.client_id)
        if self.lw_topic:
            self._send_str(self.lw_topic)
            self._send_str(self.lw_msg)
        if self.user is not None:
            self._send_str(self.user)
            self._send_str(self.pswd)
        resp = self.sock.read(4)
        assert resp[0] == 0x20 and resp[1] == 0x02
        if resp[3] != 0:
            raise MQTTException(resp[3])
        return resp[2] & 1

    def disconnect(self):
        self.sock.write(b"\xe0\0")
        self.sock.close()

    def ping(self):
        self.sock.write(b"\xc0\0")

    def publish(self, topic, msg, retain=False, qos=0):
        pkt = bytearray(b"\x30\0\0\0")
        pkt[0] |= qos << 1 | retain
        sz = 2 + len(topic) + len(msg)
        if qos > 0:
            sz += 2
        assert sz < 2097152
        i = 1
        while sz > 0x7F:
            pkt[i] = (sz & 0x7F) | 0x80
            sz >>= 7
            i += 1
        pkt[i] = sz
        # print(hex(len(pkt)), hexlify(pkt, ":"))
        self.sock.write(pkt, i + 1)
        self._send_str(topic)
        if qos > 0:
            self.pid += 1
            pid = self.pid
            struct.pack_into("!H", pkt, 0, pid)
            self.sock.write(pkt, 2)
        self.sock.write(msg)
        if qos == 1:
            while 1:
                op = self.wait_msg()
                if op == 0x40:
                    sz = self.sock.read(1)
                    assert sz == b"\x02"
                    rcv_pid = self.sock.read(2)
                    rcv_pid = rcv_pid[0] << 8 | rcv_pid[1]
                    if pid == rcv_pid:
                        return
        elif qos == 2:
            assert 0

    def subscribe(self, topic, qos=0):
        assert self.cb is not None, "Subscribe callback is not set"
        pkt = bytearray(b"\x82\0\0\0")
        self.pid += 1
        struct.pack_into("!BH", pkt, 1, 2 + 2 + len(topic) + 1, self.pid)
        # print(hex(len(pkt)), hexlify(pkt, ":"))
        self.sock.write(pkt)
        self._send_str(topic)
        self.sock.write(qos.to_bytes(1, "little"))
        while 1:
            op = self.wait_msg()
            if op == 0x90:
                resp = self.sock.read(4)
                # print(resp)
                assert resp[1] == pkt[2] and resp[2] == pkt[3]
                if resp[3] == 0x80:
                    raise MQTTException(resp[3])
                return

    # Wait for a single incoming MQTT message and process it.
    # Subscribed messages are delivered to a callback previously
    # set by .set_callback() method. Other (internal) MQTT
    # messages processed internally.
    def wait_msg(self):
        res = self.sock.read(1)
        self.sock.setblocking(True)
        if res is None:
            return None
        if res == b"":
            raise OSError(-1)
        if res == b"\xd0":  # PINGRESP
            sz = self.sock.read(1)[0]
            assert sz == 0
            return None
        op = res[0]
        if op & 0xF0 != 0x30:
            return op
        sz = self._recv_len()
        topic_len = self.sock.read(2)
        topic_len = (topic_len[0] << 8) | topic_len[1]
        topic = self.sock.read(topic_len)
        sz -= topic_len + 2
        if op & 6:
            pid = self.sock.read(2)
            pid = pid[0] << 8 | pid[1]
            sz -= 2
        msg = self.sock.read(sz)
        self.cb(topic, msg)
        if op & 6 == 2:
            pkt = bytearray(b"\x40\x02\0\0")
            struct.pack_into("!H", pkt, 2, pid)
            self.sock.write(pkt)
        elif op & 6 == 4:
            assert 0

    # Checks whether a pending message from server is available.
    # If not, returns immediately with None. Otherwise, does
    # the same processing as wait_msg.
    def check_msg(self):
        self.sock.setblocking(False)
        return self.wait_msg()
Regardless, I printed the bytearray (which you can see just before the error occurs) and I will do more reading regarding how the protocol works to see if this is actually the problem. Just thought I would post this incase you or someone else sees something I am missing.

The WROOM is also flashed with Micropython v1.18 and both libraries work on the WROOM, but not on the S2, which is odd...

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: ESP32-S2 MQTT subscriber "OSError: [Errno 113] ECONNABORTED"

Post by pythoncoder » Mon Mar 14, 2022 11:33 am

As you see, I've not even managed to get a REPL on the S2 so I can't test. Are we being told something by the fact that the firmware doesn't actually work properly?

If mqtt_simple fails then my library has no chance. However it is simple, so debugging will be easier ;)

I do suggest, given that you have a custom board, that you do a more rigorous test of WiFi connectivity using other protocols. You might try FTP, serving a web pages or doing a socket connection.
Peter Hinch
Index to my micropython libraries.

ash10
Posts: 11
Joined: Tue Mar 08, 2022 1:43 pm

Re: ESP32-S2 MQTT subscriber "OSError: [Errno 113] ECONNABORTED"

Post by ash10 » Tue Mar 15, 2022 3:43 am

pythoncoder wrote:
Mon Mar 14, 2022 11:33 am
Are we being told something by the fact that the firmware doesn't actually work properly?

No innuendo from my side, I was just noting that it's odd, because so far everything that I have used in MicroPython v1.18 on the S2 has worked without hassle. Figuring out the REPL took a bit of time, but once that was sorted I haven't had much issues.

I'll keep going with mqtt_simple and see if I get anywhere, will post here if I figure anything out.

I learnt about uasyncio through your library and went through your tutorial, which was great.
It's a pretty nifty library and I plan to make use of it after playing with it a bit more.

I will also test out the WiFi a bit more rigorously as you suggested.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: ESP32-S2 MQTT subscriber "OSError: [Errno 113] ECONNABORTED"

Post by pythoncoder » Tue Mar 15, 2022 8:36 am

No innuendo from my side
Sorry, I was suggesting that the firmware might be telling us something - a lack of testing perhaps?

The official umqtt.simple library uses blocking sockets. Its failure suggests that there may be something wrong with the socket library, and the specific point where it failed with my library suggests that writing is unreliable. If so, this would impact other protocols and going down the rabbit-hole of debugging umqtt.simple might not be the most effective way to proceed.

What I would do (if I had an S2 which actually ran MP ;) ) would be to write a script which repeatedly wrote to a socket, with a matching read script running on a PC. Check that this works on an ESP32, then repeat on the S2. One option would be to try this demo. The server runs on your PC, and the client on the device. You'll need to adapt the IP address of the PC and possibly scrap the heartbeat which just flashes an LED.

If a failure can be demonstrated with a simple test script, a ticket could be raised on GitHub.
Peter Hinch
Index to my micropython libraries.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: ESP32-S2 MQTT subscriber "OSError: [Errno 113] ECONNABORTED"

Post by pythoncoder » Thu Mar 17, 2022 3:04 pm

OK, I now have a USB breakout on GPIO19 and 20 so I have a REPL :)

I have run my range_ex.py demo, pointed at test.mosquitto.org, and it works.

However there is an oddity with the REPL which I've confirmed with other, much simpler, uasyncio code such as rate.py. The output sometimes stalls until I press a key on the PC. This means that range_ex.py appears to be doing nothing. Pressing Enter or checking a subscription to the publications verifies that the demo is running.

None of this explains the exception you are seeing, I'm afraid.

Can you confirm the strange REPL behaviour, perhaps with rate.py? Here is the sort of output you should see within ten seconds:

Code: Select all

MicroPython v1.18-221-g94a9b5066-dirty on 2022-03-17; ESP32S2 module with ESP32S2
Type "help()" for more information.
>>> import rate
Testing 100 coros for 2secs
Testing 200 coros for 2secs
Testing 500 coros for 2secs
Testing 1000 coros for 2secs
Coros  100  Iterations/sec  1320  Duration 757us
Coros  200  Iterations/sec  1330  Duration 751us
Coros  500  Iterations/sec  1334  Duration 749us
Coros 1000  Iterations/sec  1442  Duration 693us
>>> 
Peter Hinch
Index to my micropython libraries.

ash10
Posts: 11
Joined: Tue Mar 08, 2022 1:43 pm

Re: ESP32-S2 MQTT subscriber "OSError: [Errno 113] ECONNABORTED"

Post by ash10 » Tue Mar 22, 2022 12:49 pm

Apologies for the late response. Last week was so busy.
Sorry, I was suggesting that the firmware might be telling us something - a lack of testing perhaps?
Oh, my mistake, I agree, the more testing the better, especially in light of the REPL not appearing on the UART port after flashing.

I tested out rate.py a few times (+- 10) and it does not seem to stall at all.

Here is the output:

Code: Select all

>>> %Run -c $EDITOR_CONTENT
Testing 100 coros for 2secs
Testing 200 coros for 2secs
Testing 500 coros for 2secs
Testing 1000 coros for 2secs
Coros  100  Iterations/sec  1507  Duration 663us
Coros  200  Iterations/sec  1554  Duration 643us
Coros  500  Iterations/sec  1518  Duration 658us
Coros 1000  Iterations/sec  1605  Duration 623us
I also tried range_ex.py, that failed to connect to the broker.

This was the output:

Code: Select all

>>> %Run -c $EDITOR_CONTENT
Checking WiFi integrity.
Got reliable connection
Connecting to broker.
Connection failed.
So I removed try except statement so that I could see the error. Its the same as before.

Code: Select all

>>> %Run -c $EDITOR_CONTENT
Checking WiFi integrity.
Got reliable connection
Connecting to broker.
Traceback (most recent call last):
  File "<stdin>", line 93, in <module>
  File "uasyncio/core.py", line 1, in run
  File "uasyncio/core.py", line 1, in run_until_complete
  File "uasyncio/core.py", line 1, in run_until_complete
  File "<stdin>", line 64, in main
  File "mqtt/mqtt_as.py", line 523, in connect
  File "mqtt/mqtt_as.py", line 248, in _connect
  File "mqtt/mqtt_as.py", line 188, in _as_write
OSError: [Errno 113] ECONNABORTED
I think it's something to do with the sockets as you suggested.

Post Reply