GithubHelp home page GithubHelp logo

Comments (9)

Namoshek avatar Namoshek commented on June 30, 2024 1

@Sididi When you run $mqtt->loop(), the process which executes the Artisan command is being blocked by the MQTT client. The only possible way to run other code in parallel (well, not really concurrently, but invoked by the MQTT client) is through subscription callbacks and loop event handlers.

But why do you need this anyway? If you only want to publish data, you can of course just call MQTT::publish('my/topic', 'my message') in your controller or wherever. Doing so will open one connection to the MQTT broker per request though, so you should not use a static client id.

from laravel-client.

Namoshek avatar Namoshek commented on June 30, 2024 1

Alright, this makes sense (and is also a good thing to avoid). In this case, there are two quite common options:

  1. You can create a queued job for each message to publish. There will be one MQTT client per queue worker, so the number is easily calculatable.

    • Pros: Easy to develop and within the Laravel best practices
    • Cons: If you publish messages infrequently, the MQTT client will lose its connection regularly
  2. You can build your own MQTT publish queue worker which fetches new messages to publish from a queue and publishes them within a loop. Something like this (please be aware this has intentionally no error handling, etc.):

    $mqtt = MQTT::connection();
    $mqtt->registerLoopEventHandler(function (MqttClient $mqtt, float $elapsedTime) {
        $nextMessage = \Redis::lpop('mqtt-messages-to-publish');
        if ($nextMessage === null) {
            // TODO: maybe sleep for a bit to avoid polling Redis too fast
            return;
        }
    
        $message = json_decode($nextMessage);
        $mqtt->publish($message->topic, $message->content, $message->qos, $message->retain);
    });
    
    $mqtt->loop(true);
    • Pros: The MQTT connection will be kept open (via ping/pong) and you have full control over the number of workers/connections
    • Cons: More effort to develop, although not by much

from laravel-client.

Sididi avatar Sididi commented on June 30, 2024 1

Option 2 definitely does the job, thank you kindly
I want to congratulate you for always answering so quickly and with code example on each of your issues, truly a gentleman

from laravel-client.

Namoshek avatar Namoshek commented on June 30, 2024

Hi there, glad you like the package and find it useful.

I understand your question, but I have to tell you that your current approach, where the subscription is made within a controller, will not work as expected. By default, PHP requests are limited to 30 seconds of execution time and require a client to keep the connection to the server open, to prevent the request from being aborted. To keep the connection between client and server open, you will also need to ensure proper connection timeouts are in place. All in all, using such subscriptions is bad application design and is not how you would implement it in Laravel.

Instead, you should perform subscriptions within a console command. The command can be started (and restarted in case of a crash) by something like supervisor (this article is for the queue:work command, but works similar for other commands).

By using a command, the data collection runs entirely as daemon on the server and does not require a client (i.e. web browser) to be open at all. In such case, you can then implement the dashboard which displays the collected data in a controller.

from laravel-client.

ioiofadhil avatar ioiofadhil commented on June 30, 2024

i see, understandable. Supervisor the queue looks convicing. Thank you for the reply ! Have a great day !

from laravel-client.

MCKevmeister avatar MCKevmeister commented on June 30, 2024

Running $mqtt->loop() anywhere, even in a artisan command causes the program to hang. Supervisor only ensures that the script is kept running, but if it won't finish then it is unable to do anything?

I've tried to follow the examples in examples repo but all of the subscription examples close the connection which won't keep the subscription connection open.

I'm not sure how to make the daemon run in laravel when the loop function is called without it blocking the laravel application

from laravel-client.

Namoshek avatar Namoshek commented on June 30, 2024

The whole point of the $mqtt->loop() method is that it is blocking. It is an event loop, which means it does the following things over and over again:

  • Read incoming data from the MQTT broker
  • Process and deliver the received data to subscription callbacks
    (i.e. the function you register with $mqtt->subscribe($topic, $callback))
  • Send outgoing data (published messages, retries, etc.) to the MQTT broker

Because $mqtt->loop() is blocking and intended to be run forever, it is not recommended to run this method in a controller during a request. It should only be called in a console command where the process is able to run forever.

For console commands, the ext_pcntl example is the most suitable. Just remove line 41 and you are good to go. If you wonder what it does:

  • It registers a signal handler for SIGINT (ctrl + c) which interrupts the $mqtt->loop() if running
  • It connects to the MQTT broker and subscribes to a topic, then infinitely runs using $mqtt->loop() according to above description
  • It disconnects gracefully after being interrupted via a SIGINT signal

Please be aware that ext_pcntl only works on UNIX based systems.

from laravel-client.

Sididi avatar Sididi commented on June 30, 2024

Hi there, glad you like the package and find it useful.

I understand your question, but I have to tell you that your current approach, where the subscription is made within a controller, will not work as expected. By default, PHP requests are limited to 30 seconds of execution time and require a client to keep the connection to the server open, to prevent the request from being aborted. To keep the connection between client and server open, you will also need to ensure proper connection timeouts are in place. All in all, using such subscriptions is bad application design and is not how you would implement it in Laravel.

Instead, you should perform subscriptions within a console command. The command can be started (and restarted in case of a crash) by something like supervisor (this article is for the queue:work command, but works similar for other commands).

By using a command, the data collection runs entirely as daemon on the server and does not require a client (i.e. web browser) to be open at all. In such case, you can then implement the dashboard which displays the collected data in a controller.

Hi Namoshek,

Currently doing something pretty similar, I have a simple artisan command initializing connection and running the infinite loop so it handles subscriptions / sent messages (all in QoS 2)

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Contracts\Console\Isolatable;

use PhpMqtt\Client\Facades\MQTT;

class RunMQTTLoop extends Command implements Isolatable
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'mqtt:start';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Starts a loop handling all MQTT events';

    /**
     * Determine when an isolation lock expires for the command.
     *
     * @return \DateTimeInterface|\DateInterval
     */
    public function isolationLockExpiresAt()
    {
        return now();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        /** @var \PhpMqtt\Client\Contracts\MqttClient $mqtt */
        $this->info('Connecting to MQTT broker...');
        $mqtt = MQTT::connection();

        $this->info('Connected. Now initializing loop.');
        $mqtt->loop();

        return Command::SUCCESS;
    }
}

But I would like to access that instance of php-mqtt ($mqtt variable) outside of my artisan command so I can use it to send messages and register new subscription channels interactively
By any chance, have you any idea how to properly do it in Laravel? Many thanks

from laravel-client.

Sididi avatar Sididi commented on June 30, 2024

Thank you for the fast answer Namoshek
I wanted to do it this way precisely to avoid opening a connection on each request as I will possibly have several dozens publish per second (each independant)

from laravel-client.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.