From IoT to PHP: using Laravel to read values from PLCs

|

5 years ago I started my employment at a construction company that acquired an industrial automation company. I’ve come to know so much about the industrial automation space, IoT, and how to tie things together using a different approach than most: Using PHP and Laravel to retrieve and process IoT data from PLCs.

High level overview

In order to read values from PLCs, there is a certain amount of prerequisite information you have to go through in order to understand what the heck is going on. Here’s the short and sweet of it:

  • We have a computer with a specialized operating system attached to some wires that are interpreted as either boolean values, or numbers
  • We have another, more general purpose, computer that is responsible for fetching data from that specialized computer
  • We need some way for both computers to communicate to each other

What the heck is a PLC?

Programmable Logic Controllers (PLC) are specialized computers used in the industrial automation and IoT industries. Most are typically built for more rugged environments, so they can be rated for a certain amount of vibration, certain temperatures, and can be made out of sturdier metals. While most commercial computers nowadays are built purely to run other software, PLCs are built to process external digital/analog signals that are attached physically or over a network. They can also be connected to other more complex devices, and even other PLCs!

PLCs typically support other programming languages, most commonly through proprietary variations of the IEC_61131-3 standard, which is a standard that defines data types and provides the syntax for both text-based and visual diagram-based programming languages. Not all PLCs require you to follow the IEC 61131-3 standard. Some will allow you to upload your own C++ code to them, giving you access to better tooling like IDEs and CLIs.

Signals: From wires to numbers

Now that you know that a PLC’s main purpose is to process signals, let’s dive a bit deeper on what exactly a “signal” is. A signal is technically data transferred between the PLC and the physical world.

In the world of PLCs there are two main types of signals: Digital, and Analog.

For example, A single wire can be interpreted as a simple signal; A signal that is near and dear to our programmer hearts: a 1 or a 0, otherwise known as binary! Simple signals like this are known as Digital Signals and they are often used for things like switches and relays.

Analog Signals on the other hand represent a range of values and are often used for more complex measurements like temperature, pressure, or water level. In simpler terms, an analog signal is an electrical current that converts into a measurement. For example, lets say we have a temperature sensor that can read temperatures from 0 degrees Fahrenheit to 120 degrees Fahrenheit, and the device sends anywhere from 4 mA to 20mA to represent the full range of degrees.

Here is a table of what the conversions would look like:

Going IoT: Transmitting Values

Now that you understand how PLCs take in external signals and convert them into numerical data, the next step is getting that data out of the PLC so you can further analyze, compile, and report on it!

This is where things turn into a very “choose your own path” kind of adventure because there are so many ways that we can transmit this data and each transmission mechanism can have bigger impacts on downstream systems.

This is by no means an exhaustive list, but here are a few different ways that PLCs can transmit data to other devices:

  • Modbus: One of the most used protocols that supports 2 wire and 4 wire comms, as well as TCP/IP and even UDP/IP.
  • MQTT: A lightweight and efficient messaging protocol for rapidly transmitting data over a network.
  • HTTPS: A few modern (and expensive) PLCs support REST APIs.
  • CSV/SFTP: Pretty old-school way of transmitting data, but it’s possible.

This post will only cover one of the many ways to receive values, the one that I believe is probably the simplest one and most widely available: MQTT.

Diving deeper into MQTT

MQTT stands for Message Queuing Telemetry Transport, and is a messaging protocol that describes how to transmit data from the “client” (the device) to the MQTT server that understands how to interpret the message.

What makes this different than your typical HTTP request?

Well, there are a few key differences. For starters, HTTP follows a request/response model, meaning that one computer has to initiate a request and wait until it receives a response.


MQTT on the other hand, is a publish/subscribe model, meaning that a server can handle messages from multiple devices (Clients) and forward those messages to other applications (Subscribers).

Processing data with Laravel

Now that we know how data is transformed from electrical signals to digital values, and how those values are transmitted over a network, let’s talk about how applications receive those values and what you can do with them.

For brevity, I won’t go into the nitty-gritty and show you how to set up a Laravel project. But I will talk about some of the mechanisms that Laravel offers you that we can leverage in our IoT endeavors. Namely Laravel commands and events!

Note: If you’re copy/pasting the code into your Laravel application, please have the php-mqtt/laravel-client package installed.

Laravel Commands

Laravel ships with its own CLI tool named Artisan. Artisan has a ton of commands that allow you to generate boilerplate, run migrations, etc. The Artisan command relevant to use right now is: php artisan make:command.

make:command generates all of the boilerplate code needed to create a new Artisan command. Running php artisan make:command StartMqttListener created a new file at app/Console/Commands/StartMqttListener.php.

Here is a Command that connects to an MQTT broker and subscribes to a particular topic:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Log;
use PhpMqtt\Client\Facades\MQTT;

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

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Listens for MQTT messages.';

    /**
     * Execute the console command.
     */
    public function handle(): int
    {
        Log::debug('[MQTT] Connecting to broker');

        $connection = MQTT::connection();
        if ($connection->isConnected()) {
            Log::debug('[MQTT] Successfully connected to broker');
        }

        $connection->subscribe('TOPIC_HERE', function (string $topic, string $message) {
            // TODO: Do something with the message
        });

        $connection->loop();

        Log::debug('[MQTT] Gracefully disconnected from broker');

        return Command::SUCCESS;
    }
}

Now, when we run php artisan mqtt:start, the process will listen to the desired MQTT topic, and we can then move onto the next Laravel concept: Events!

Laravel Events

Similar to commands, we can run php artisan make:event to create the boilerplate necessary to define an Event! I ran php artisan make:event ReceivedMqttMessage, which created the event at app/Events/MqttMessageReceived.php. This is what I ended up with:

<?php

namespace App\Events;

use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class MqttMessageReceived
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     */
    public function __construct(public readonly string $message)
    {
    }
}

Notice how $message is a public property, meaning that it’s accessible to outside classes. This is useful because we will now create the class that consumes this event by running php artisan make:listener ProcessMqttMessage.

Here is the listener code I came up with:

<?php

namespace App\Listeners;

use App\Events\MqttMessageReceived;
use Illuminate\Contracts\Queue\ShouldQueue;

class ProcessMqttMessage implements ShouldQueue
{
    /**
     * Create the event listener.
     */
    public function __construct()
    {
    }

    /**
     * Handle the event.
     */
    public function handle(MqttMessageReceived $event): void
    {
        // TODO: Parse whatever is inside of $event->message
        $text = $event->message;
    }
}

Now, we can modify our Artisan command to dispatch an event every time we receive a message, like so:

<?php

namespace App\Console\Commands;

use App\Events\MqttMessageReceived;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use PhpMqtt\Client\Facades\MQTT;

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

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Listens for MQTT messages.';

    /**
     * Execute the console command.
     */
    public function handle(): int
    {
        Log::debug('[MQTT] Connecting to broker');

        $connection = MQTT::connection();
        if ($connection->isConnected()) {
            Log::debug('[MQTT] Successfully connected to broker');
        }

        $connection->subscribe('TOPIC_HERE', function (string $topic, string $message) {
            MqttMessageReceived::dispatch($message);
        });

        $connection->loop();

        Log::debug('[MQTT] Gracefully disconnected from broker');

        return Command::SUCCESS;
    }
}

From there, process it how you like! Some devices send JSON over MQTT, so you can use something like json_decode to decode it to something you can work with.

Conclusion

In this post, we’ve demystified the workings of PLCs, delved into the intricacies of digital and analog signals, and explored different methods of data transmission, finally setting our sights on MQTT as our go-to protocol. The code snippets provided offer a straightforward way to set up your Laravel application to interact with IoT devices. And while we’ve focused on Laravel here, the principles can apply to other web frameworks, languages, and even protocols.

Working with hardware can seem like a pretty complex task, so I hope that this post gave you some ideas on some ideas or projects. If you do, feel free to let me know about em by tagging me on twitter @iPwnPancakes or on LinkedIn.