How to get latest message in an MQTT topic from PHP?

I’m able to post and subscribe to MQTT topics in c++ using an ESP32 microcontroller, but I’m having trouble retrieving the last message from PHP.

I’m posting a message with the “persistent” flag set to true to the topic “esp32/wrybread_test_mode” (on the broker broker.emqx.io), and I can see the messages arriving in the MQTTX app.

However I can’t figure out how to retrieve the last message posted there from PHP. And since my PHP page just runs once I don’t think subscribing will work for me, since the page just opens and closes. In other words the callback doesn’t have time to run.

Here’s my code so far:

require('vendor/autoload.php');
use \PhpMqtt\Client\MqttClient;
use \PhpMqtt\Client\ConnectionSettings;

$server   = 'broker.emqx.io';
$port     = 1883;
$clientId = 'test-publisher-wrybread';

$mqtt = new \PhpMqtt\Client\MqttClient($server, $port, $clientId);
$mqtt->connect();


$mqtt->subscribe("esp32/wrybread_test_mode", function ($topic, $message) {
	printf("Received message on topic [%s]: %s\n", $topic, $message);
}, 0);

$mqtt->disconnect();


Any idea how I can retrieve the latest message in a topic using PHP, on a shared host that I don’t have control over? In other words where the page can only run once, without the ability for a longterm subscription to a topic?

A solution in Python would also work for me in this case. So far I’m only seeing the ability to create a callback when a new message is received, as opposed to browsing a topic and obtaining the last message posted (in the past).

Hi. Do you mean the message with the persistence flag set to true is the retained message?

If so, you only need to connect and subscribe again to receive the message.

If you don’t receive the message, you can capture the packet to help you confirm whether the MQTT Broker has not delivered it, or there is a problem with your code that caused the message to arrive , but the callback is not triggered.

I just mean a simple way to check the message from PHP. Here’s the solution I came up with, but it’s fairly slow, about .5 seconds on average. And if the topic doesn’t exist it might hang forever, or until your server terminates it. Would be nice if there was just a way to check for a topic without the pubsub model.


// mqtt library
require('vendor/autoload.php');
use \PhpMqtt\Client\MqttClient;
use \PhpMqtt\Client\ConnectionSettings;

$start_time = microtime(true);

if (1) {
	
	$server   = 'broker.emqx.io';
	$port     = 1883;
	$clientId = 'test-publisher-wrybread';

	$mqtt = new \PhpMqtt\Client\MqttClient($server, $port, $clientId);
	$mqtt->connect();

	$mqtt_topic = "esp32/test_message";
	$mqtt->subscribe($mqtt_topic, function ($topic, $message) {
		global $start_time;
		//printf("Received message on topic [%s]: %s\n", $topic, $message);
		
		$finish_time = microtime(true) - $start_time;
		
		$current_mode = $message;
		$arr = array(
			'current_mode'	=> $current_mode, 
			'epoch'	=> time(), 
			'execution_time'	=> $finish_time,
		);

		$json_string = json_encode($arr);
		print $json_string;
		
		// this should work to end $mqtt->loop(), but oddly doesn't
		//$mqtt->interrupt();
		
		// this works
		exit();

	}, 0);

	$mqtt->loop();

	$mqtt->disconnect();

}

If you just publish a normal message, the message will only be forwarded to the current subscriber, and any subsequent subscribers will not receive the message.

This is a feature of MQTT, which is different from message queues.

However, MQTT provides retained messages. Each topic can set a maximum of one retained message. The retained message can be updated. It will be forwarded to you when you establish a subscription, and trigger your message arrival callback.

Aha I found a nice solution, I thought I’d post it in case anyone else comes this way.

The problem with the sample on this site is that if there’s no message in that topic, the PHP script will just hang forever. Adding this will kill the script after 10 seconds:

// Register a hook which is called once per loop and which interrupts the loop after some time.
// Otherwise this will just run endlessly if there's nothing in the topic
$mqtt->registerLoopEventHandler(function (MqttClient $mqtt, float $elapsedTime) {
    if ($elapsedTime >= 10) {
		print "Killing since we never got a topic!";
        $mqtt->interrupt();
    }
});

(Can adjust the timeout from 10 seconds to whatever works better for you).

So the complete working code is:

// mqtt library from emqx.com
require('vendor/autoload.php');
use \PhpMqtt\Client\MqttClient;
use \PhpMqtt\Client\ConnectionSettings;

$start_time = microtime(true);
	
$server   = 'broker.emqx.io';
$port     = 1883;
$clientId = 'test-publisher-wrybread';

$mqtt = new \PhpMqtt\Client\MqttClient($server, $port, $clientId);
$mqtt->connect();

// Register a hook which is called once per loop and which interrupts the loop after some time.
// Otherwise this will just run endlessly if there's nothing in the topic
$mqtt->registerLoopEventHandler(function (MqttClient $mqtt, float $elapsedTime) {
    if ($elapsedTime >= 10) {
		print "Killing since we never got a topic!";
        $mqtt->interrupt();
    }
});

$mqtt_topic = "esp32/test_message";
$mqtt->subscribe($mqtt_topic, function ($topic, $message) {
	global $start_time;
	//printf("Received message on topic [%s]: %s\n", $topic, $message);
	
	$finish_time = microtime(true) - $start_time;
	
	$current_mode = $message;
	$arr = array(
		'current_mode'	=> $current_mode, 
		'epoch'	=> time(), 
		'execution_time'	=> $finish_time,
	);

	$json_string = json_encode($arr);
	print $json_string;
	
	// this should work to end $mqtt->loop(), but oddly doesn't
	//$mqtt->interrupt();
	
	// this works
	exit();

}, 0);

$mqtt->loop();
$mqtt->disconnect();