MQTT Connection Blocking CoppeliaSim Simulation – Need Async Solution

Typically: "How do I... ", "How can I... " questions
Post Reply
Bochra
Posts: 2
Joined: 14 Mar 2025, 13:40

MQTT Connection Blocking CoppeliaSim Simulation – Need Async Solution

Post by Bochra »

Hello,

I’m using CoppeliaSim for robot simulation and LuaMQTT to connect the robot to a local Mosquitto MQTT broker. The issue is that the MQTT connection is blocking the simulation, preventing the robot from moving based on MQTT payloads received in topics.

I’m trying to use async code to handle the MQTT connection without blocking the simulation, but it’s not working as expected. When the MQTT connection is established, the simulation gets blocked.

I’ve used coroutines in CoppeliaSim and LuaMQTT’s callback functions, but the MQTT connection still seems to block the simulation.

Has anyone managed to make this work asynchronously in CoppeliaSim? Any help would be appreciated!

PS: Just to be clear, the broker works, and both publish and subscribe functions work. However, when the MQTT connection is established, the simulation crashes.

Useful links:

The link to my Lua script: https://www.blackbox.ai/share/eb9b70e1- ... 10981d41ae
The LuaMQTT library I am using: https://github.com/xHasKx/luamqtt
Thank you in advance for your help!

coppelia
Site Admin
Posts: 10714
Joined: 14 Dec 2012, 00:25

Re: MQTT Connection Blocking CoppeliaSim Simulation – Need Async Solution

Post by coppelia »

Hello,

CoppeliaSim's main loop uses a single thread that serves all scrips. If that thread gets blocked, then the whole simulator is blocked.
Now of course, if a script uses a library that launches its own thread, then CoppeliaSim won't block when the other thread blocks. But that other thread is not allowed to access CoppeliaSim API functions.

Not sure how LuaMQTT works in details. But in your code, you create a coroutine, but only resume the coroutine once, during initialization. You need to resume the coroutine at least once per simulation step, e.g. in the sensing section, e.g.:

Code: Select all

function sysCall_init()
    corout = coroutine.create(coroutineMain)
end

function sysCall_sensing()
    if coroutine.status(corout) ~= 'dead' then
        local ok, errorMsg = coroutine.resume(corout)
        if errorMsg then
            error(debug.traceback(corout, errorMsg), 2)
        end
    end
end

function coroutineMain()
    ...
end
Cheers

Bochra
Posts: 2
Joined: 14 Mar 2025, 13:40

Re: MQTT Connection Blocking CoppeliaSim Simulation – Need Async Solution

Post by Bochra »

Thank you very much for your help! your solution worked perfectly.

For anyone who might find it useful, here is the working code, it connects CoppeliaSim to a local MQTT broker in a non-blocking way :

Code: Select all

function sysCall_init()
    sim = require('sim')

    -- Initialize MQTT
    package.path = package.path .. ";C:/Program Files/CoppeliaRobotics/CoppeliaSimEdu/lua/mqtt/?.lua"
    print("Package path updated:", package.path)

    local mqtt = require("mqtt")
    local ioloop = require("mqtt.ioloop").get()  -- Get the default ioloop instance

    if not mqtt then
        error("Failed to load MQTT library. Check the path and ensure the library exists.")
    end

    -- Create MQTT client
    client = mqtt.client{
        uri = "127.0.0.1:1883", 
        id = "robot_client",
        clean = true
    }

    if not client then
        error("Failed to create MQTT client.")
    end

    print("Created MQTT client:", client)

    -- Set up the MQTT client on events
    client:on{
        connect = function(connack)
            if connack.rc ~= 0 then
                print("Connection to broker failed:", connack:reason_string(), connack)
                return
            end
            print("Connected to broker:", connack)  

            -- Subscribe to the topic after connection
            assert(client:subscribe{
                topic = "robot/commands",
                qos = 2,
                callback = function(suback)
                    print("Subscribed to topic:", suback)
                end
            })
        end,

        message = function(msg)
            assert(client:acknowledge(msg))  
            print("Received message:", msg.topic, msg.payload)
        end,

        error = function(err)
            print("MQTT client error:", err)
        end,

        close = function()
            print("MQTT connection closed")
        end
    }

    -- Attach client to the ioloop
    local success, err = pcall(function()
        ioloop:add(client)
    end)
    if not success then
        print("Failed to add client to ioloop:", err)
        return
    end
    print("Client added to ioloop")

    -- Create the coroutine for the MQTT loop
    mqttCoroutine = coroutine.create(function()
        while true do
            local success, err = pcall(function()
                ioloop:iteration(0.1)  -- Small timeout to prevent excessive CPU usage
            end)
            if not success then
                print("Error in ioloop iteration:", err)
            end
            coroutine.yield()  -- Yield control back to CoppeliaSim
        end
    end)
end

-- Resume the coroutine in the sensing function
function sysCall_sensing()
    if coroutine.status(mqttCoroutine) ~= 'dead' then
        local ok, errorMsg = coroutine.resume(mqttCoroutine)
        if errorMsg then
            error(debug.traceback(mqttCoroutine, errorMsg), 2)
        end
    end
end

coppelia
Site Admin
Posts: 10714
Joined: 14 Dec 2012, 00:25

Re: MQTT Connection Blocking CoppeliaSim Simulation – Need Async Solution

Post by coppelia »

CoppeliaSim can handle the creation of the (default) coroutine for you, simply by having the sysCall_thread() function in place, e.g. your code would become:

Code: Select all

function sysCall_init()
    sim = require('sim')

    -- Initialize MQTT
    package.path = package.path .. ";C:/Program Files/CoppeliaRobotics/CoppeliaSimEdu/lua/mqtt/?.lua"
    print("Package path updated:", package.path)

    local mqtt = require("mqtt")
    local ioloop = require("mqtt.ioloop").get()  -- Get the default ioloop instance

    if not mqtt then
        error("Failed to load MQTT library. Check the path and ensure the library exists.")
    end

    -- Create MQTT client
    client = mqtt.client{
        uri = "127.0.0.1:1883", 
        id = "robot_client",
        clean = true
    }

    if not client then
        error("Failed to create MQTT client.")
    end

    print("Created MQTT client:", client)

    -- Set up the MQTT client on events
    client:on{
        connect = function(connack)
            if connack.rc ~= 0 then
                print("Connection to broker failed:", connack:reason_string(), connack)
                return
            end
            print("Connected to broker:", connack)  

            -- Subscribe to the topic after connection
            assert(client:subscribe{
                topic = "robot/commands",
                qos = 2,
                callback = function(suback)
                    print("Subscribed to topic:", suback)
                end
            })
        end,

        message = function(msg)
            assert(client:acknowledge(msg))  
            print("Received message:", msg.topic, msg.payload)
        end,

        error = function(err)
            print("MQTT client error:", err)
        end,

        close = function()
            print("MQTT connection closed")
        end
    }

    -- Attach client to the ioloop
    local success, err = pcall(function()
        ioloop:add(client)
    end)
    if not success then
        print("Failed to add client to ioloop:", err)
        return
    end
    print("Client added to ioloop")
end

function sysCall_thread()
    while true do
        local success, err = pcall(function()
            ioloop:iteration(0.1)  -- Small timeout to prevent excessive CPU usage
        end)
        if not success then
            print("Error in ioloop iteration:", err)
        end
        sim.yield()  -- Yield control back to CoppeliaSim
    end
end
Cheers

Post Reply