Communicating with Smart Devices with PHP
Programming

Communicating with Smart Devices with PHP

16 min read

Smart devices surround us every day and not only in everyday life: sensors, household appliances, light bulbs, sockets and other equipment. Every day we encounter newer and smarter devices controlled via the Internet or Wi-Fi.

IoT (Internet of Things) means the Internet of smart things. It is a concept that unites physical devices into one network for data transfer and management. And it turns out that the Internet of Things is not a limitation! You can manage devices in the network using the lightweight MQTT protocol.

Hello, Habr! My name is Alexander Cherednikov and I am CTO at QTIM, a company that does custom development. In this article, based on my talk at PHP Russia, I will tell you how to communicate with smart devices using PHP.

MQTT and some of its features

MQTT is a lightweight communication protocol designed specifically for devices with limited capabilities and low bandwidth. Let’s look at some of its features.

MQTT Broker

An MQTT broker is a central node through which clients can exchange messages.

MQTT Brokers:

In practice, I had to work with Eclipse Mosquitto. We chose it for several reasons: it is lightweight, easy to configure, very fast and allows you to transmit messages instantly. You can read about the pros and cons of the others in the documentation and choose one for your needs.

Messages

Messages in MQTT are transmitted in binary form, which reduces memory and decreases the time it takes to transmit a message.

There are different types of messages from smart devices:

Fortunately, PHP can handle all of this.

Types of packages

When transmitting messages through a broker to smart devices, different types of packets are used in the message:

The packet type is indicated by the first byte at the beginning of the message. Based on it, the broker determines which message to work with. In versions MQTT 3.3.1, MQTT 5.0, these packet types may differ. You need to look at the specific packet type for a specific specification and devices.

QoS

QoS is an important parameter for guaranteeing message delivery when exchanging messages between smart devices and the server.

Types of QoS :

  1. QoS 0 - does not guarantee message delivery to the subscriber. This means that the publisher sends the message only once and does not wait for confirmation that the message has been received.
  2. QoS 1 is a more reliable delivery method. QoS 1 guarantees that the message will be delivered to the subscriber at least once. But it does not guarantee that the message will not arrive again.
  3. QoS 2 is the most reliable delivery method. QoS 2 ensures that the publisher sending the message will send it only once, without duplication.

Topics

When exchanging messages, topics are used - routes that serve to organize and filter data when transmitting a message.

home/temperaturehome/humidityoffice/temperature

Topics are similar to routes from the HTTP protocol, but they do not have query parameters for filtering.

There are some features of topics:

For example, if we want to select certain readings from certain sensors in rooms, we can use wildcards.

Single level (+) - /rooms/+/action/get

A single-level wildcard means that we will only collect information at one level. For example, we want to collect data from all temperature sensors in all rooms, without using information from other devices.

Multilevel (#) - /rooms/#

A multi-level symbol is used when it is necessary to collect information from all devices and sensors in all rooms.

The broker also has system topics, which are designated $SYS. They are intended for diagnosing the broker itself:

With their help, we can track the load, the number of clients connected to the broker, its operating time, etc. This information may be needed when diagnosing problems and when compiling metrics and graphs.

It is important that system topics do not relate to smart devices in any way, but only to message brokers. It is impossible to obtain any information from the devices themselves.

Publishers

A publisher is a publisher that sends a message about a specific topic to a broker.

Subscribers

Subscribers read these messages according to a specific topic.

For example, a smart temperature sensor sends its data to a broker; then subscribers who monitor the corresponding topic read this data. As a result, we either display the temperature to the end user, or store the data and draw conclusions, build graphs.

The image below shows how the exchange with the MQTT broker occurs.

How PHP Interacts with MQTT

Let’s look at how PHP can interact with MQTT.

Many have heard that PHP is not able to communicate with smart devices. But the MQTT broker can do this, it will receive and coordinate messages. And PHP will help us communicate with MQTT.

The simplest PHP worker looks like this:

//...
$sock = fsockopen($broker, $port, $errno, $errstr, 10);

//...
while (true) {
    $response = fread($sock, 2048);

    if (ord($response[0]) >> 4 === 3) {
        $remainingLength = ord($response[1]);
        $topicLength = ord($response[2]) << 8 | ord($response[3]);

        $msgTopic = substr($response, 4, $topicLength);
        $message = substr($response, 4 + $topicLength, $remainingLength - $topicLength - 2);
        echo "Получено сообщение в топике '$msgTopic': $message" . PHP_EOL;
    }

usleep (500000);
}

Basically, it’s a long-running loop that runs in a process and reads information from a TCP socket. This information is then parsed into message packets, the message itself, and the topic. And we can work with this information.

Libraries for working with MQTT

There are special libraries for communicating with MQTT:

https://github.com/php-mqtt/client — lightweight, easy to configure and support. I will show examples further with it. It has one feature — it does not support MQTT 5.0. If you encounter this protocol, keep this in mind.

https://github.com/simps/mqtt — built on the Swoole library.

https://github.com/aws/aws-sdk-php/tree/master/src/Iot — MQTT client is provided by the AWS library in its SDK.

How to read messages from a smart device

To read messages, we create a client and connect to it. On this client, we call the subscribe method. We specify as the first parameter which topic we want to receive the message from. In the callback, we accept the topic itself, the message and process it as needed - either save any processing logic to the database, or show it to the client. The third parameter in the subscription is QoS. This must be done so that the library, when sending a message, understands which type of packet to send to the broker for correct interaction. Then a long-lived cycle in the form of loop is launched, and the subscription will be valid until we interrupt it.

If we interrupt the subscription or an exception occurs, we will disconnect from this client to avoid creating unnecessary connections and terminate the long-running process.

try {
    $client = new MqttClient(MQTT_BROCKER_HOST, MQTT_BROCKER_PORT);
    $client->connect();

    $client->subscribe('rooms/+/temp', function (string $topic, string $message) {
        // Логика обработки сообщения
    }, MqttClient: :Q0S_AT_LEAST_ONCE);

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

} catch (MqttClientException) {
    // Логика обработки сообщения
}

You can also manage devices if you need to send a command to them. To send a command to a device, you need to publish a message that it receives on a specific topic to the topic to which this device is subscribed. We will also create a client and connect to it.

A simple example of renting a scooter:

$client = new MqttClient(MQTT_BROKER_HOST, MQTT_BROKER_PORT);
$client->connect();

$client->publish(
    topic: 'scooter/1234/rent',
    message: json_encode(['rent_number' => '4321']),
    qualityOfService: MqttClient: :QOS_AT_MOST_ONCE,
);

$client->disconnect();

The topic contains the “Scooter” segment, its number, and the rent command. In the body, we pass the rental number so that the scooter understands what rental it was turned on under. We also pass QOS as the third parameter. Here, we do not start any long-lived cycles, but simply publish a message to the topic and disconnect from the client.

Sometimes you may need to publish a message to a topic. At this point, you need to get a response from the device. Then we can complete the long-lived process so as not to keep constant subscriptions.

Sclient = new NqttClient(MQTT_BROKER HOST, MQTT_BROKER_PORT);

$client->registerLoopEventHandler(function (MqttClient $client, float $elapsedTime) {
    if ($elapsedTime >= 30) {
        $client->interrupt();
    }
});

$client->subscribe('/device/1111/update’, function (string $topic, string $message) use ($client) {
    if (substr($message, 1, 2) === 'QG") {
        // Тут логика обработки сообщения
        $client->interrupt();
    }
});

// Публикация сообщения на которой ожидаем ответ
$client->publish('/device/1111/get', json_encode(['data’ => *test']));

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

We create a client, subscribe to a certain topic. By the way, this logic is from a real device, which received the device message type in the body of the message itself. We understand the type of this message and make logic on this basis. After processing, we interrupt the message and exit the long-lived cycle, which starts just below.

Next, we publish the message. An important point is that the publication will occur after we have subscribed. It happens that we have published a message, it has time to arrive at the broker. The device has time to read this message, give a response, but the connection has not happened. At this point, we lose the message and do not know what happened at the moment of publication to the device. In the code registered by event handlers immediately after creating a client, you can perform all the same actions with subscriptions and publication. In the example above, I showed how to determine the timeout of the message response from the smartest device.

For example, the Wi-Fi or GSM connection is lost. If you have implemented a request via a button on your website or in a mobile application, you must interrupt the cycle at some point so as not to hang it for a long time. We have 30 seconds for this. Usually, devices respond somewhere around 2-3 seconds, which is not critical. If the timeout is much longer, then a queue system is implemented to process these messages.

Important:

Logger implementation

The implementation of a simple logger looks like this:

$client = new MqttClient(MQTT_BROKER_HOST, MQTT_BROKER_PORT);

$client->subscribe('#', function (string $topic, string $message) {
    $this->logger->info('Сообщение от устройства.', [
        'topic' => $topic,
        'message' => $message,
    ]);
});

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

We subscribe to all topics sent by devices. This means that we want to see the chronology with timestamps of events sent to the broker. For example, to diagnose why a message was not delivered. The topic itself and the message itself are logged. A long-lived process in the form of a loop is also launched. If this process is interrupted, we disconnect from this client.

The log from a real IoT device looks like this:

In Moscow, Kazan and St. Petersburg there is a service for sharing chargers PowerApp. For stations storing power banks, I wrote software from scratch to communicate with them. This is an example from one of the stations.

Here, update means that the message came from the device. Get means that the message was sent by the server. The CN command turns on the station. We respond that we have received the message, and then the device sends its internal information. The device contains slots where the power banks are located, and we need to understand which power bank is currently inserted, what its charge level is, and see the power bank error codes. Based on this information, we can decide whether to continue renting out the power bank.

The device sends a message in the form of a heartbeat. This happens, depending on the settings, once a minute or three times. The heartbeat shows how the power banks are charging, their current charge, and information about whether the device is online.

A message in the form of Protobuf is also from a real device like the “Take Charge” station, where the exchange takes place in exactly this form:

syntax = "proto3";

package messages. setUpVoice;

message ServerSend {
    uint32 rl_index = 1;
    uint32 rl_ivl = 2;
    uint32 rl_seq = 3;
}

message CabinetReply {
    uint32 rl_result = 1;
    uint32 rl_code = 2;
    uint32 rl_seq = 3;
}

The example above shows setting the volume in the device itself: a message sends the volume level value to the device. Then a message comes back from the device with a response code: successful or not. Everything works fine and is processed.

Problems with physical devices

There are always problems. Most of the equipment is produced on the Chinese market. When communicating directly with Chinese representatives, translation difficulties often arise. We do not always understand each other. We have to adapt.

Some critical device issues I have encountered:

MQTT as a way of communication in PHP

PHP can also exchange messages without smart devices, acting as both a publisher and a subscriber. This adds another way to choose how to exchange messages between microservices or devices.

But you will need an additional broker through which you will exchange. There are a number of nuances: messages are limited in query parameters and headers, so it will be inconvenient to communicate between services. But there is also a plus - messages are compressed into binary form and are transmitted very quickly.

Scaling

To sort out all the messages coming from devices, you can use queues. There are currently over a thousand stations in Russia that issue power banks.

Each station sends messages when the heart beats. Commands are also sent to it, and it sends messages in response. It turns out up to 3-3.5 thousand messages per second. Even if the logic for processing these messages is very large, we can easily analyze them all using Rabbit.

Horizontal scaling can be anything you want, with Docker and Kubernetes.

Everything scales perfectly. There are no practical limitations - PHP workers, PHP applications, MQTT brokers themselves scale.

Benefits of PHP in IoT

Let me summarize the advantages of PHP when communicating with smart devices: