Практические методы взаимодействия с последовательными портами в PHP

Раздел: Системное программирование -> Системное программирование

Взаимодействие с последовательными портами из PHP открывает возможности управления периферийными устройствами: микроконтроллерами, весами, принтерами чеков и промышленным оборудованием. В статье рассмотрены различные способы работы с COM-портами: от готовых библиотек до встроенных функций.

Основное решение: библиотека PHP-Serial

Библиотека php-serial (Xowap/PHP-Serial) предоставляет удобный объектно-ориентированный интерфейс для работы с последовательными портами на Windows, Linux и macOS. Установка через Composer:

composer require xowap/php-serial

Пример открытия порта, настройки параметров и записи с чтением:

<?
require 'vendor/autoload.php';

use PhpSerial\Serial;

$serial = new Serial();
$serial->deviceSet('/dev/ttyUSB0');
$serial->confBaudRate(9600);
$serial->confParity('none');
$serial->confCharacterLength(8);
$serial->confStopBits(1);
$serial->deviceOpen();

$serial->sendMessage('AT\r\n');
$response = $serial->readPort();
echo 'Ответ: ' . $response;

$serial->deviceClose();
?>

Библиотека автоматически подбирает системные вызовы. Цель: быстрая разработка с минимальными усилиями. Случаи использования: создание шлюзов, управление Arduino, чтение показаний датчиков.

Возможные проблемы и решения

  • Ошибка открытия порта – не хватает прав на устройство. Решение: добавить пользователя в группу dialout (Linux) или sudo chmod 666 /dev/ttyUSB0.
  • Библиотека не найдена – не установлен Composer или не выполнен composer install.
  • Порт занят – другое приложение (MobaXterm, screen) использует порт. Освободить порт.

Вариант 1. Как использовать встроенные функции fopen на Linux?

Цель: работа без внешних зависимостей. Случай: простые скрипты на сервере с ограниченными правами.

Открытие файла устройства и настройка через утилиту stty:

<?
$port = '/dev/ttyUSB0';
shell_exec("stty -F $port 9600 cs8 -cstopb -parenb 2>&1");
$fp = fopen($port, 'w+');
stream_set_blocking($fp, false);
fwrite($fp, 'AT\r\n');
usleep(100000);
$response = stream_get_contents($fp);
fclose($fp);
echo $response;
?>

Проблемы

  • Неверная настройка stty – параметры могут не примениться. Проверить через stty -F $port -a.
  • Блокировка – без stream_set_blocking(false) скрипт зависнет при отсутствии данных.

Вариант 2. Как подключиться к COM-порту на Windows?

Цель: интеграция с Windows‑приложениями. Случай: унаследованные системы, торговое оборудование.

Использование команды mode и fopen:

<?
$port = 'COM3:';
shell_exec("mode $port BAUD=9600 DATA=8 PARITY=N STOP=1 &");
$fp = fopen($port, 'w+');
fwrite($fp, 'AT\r\n');
usleep(100000);
$response = fread($fp, 100);
fclose($fp);
echo $response;
?>

Типичные ошибки

  • Порт не открывается – недостаточно прав или отсутствует драйвер. Запустить PHP от администратора.
  • mode не возвращает ошибку – команда может выполниться «молча». Проверить exec с захватом вывода.

Вариант 3. Как использовать расширение DIO?

Цель: низкоуровневый контроль параметров линии. Случай: встроенные системы с поддержкой DIO.

Устанавливается через pecl install dio. Пример настройки и записи:

<?
$fd = dio_open('/dev/ttyS0', O_RDWR);
dio_tcsetattr($fd, array(
  'baud' => 9600,
  'bits' => 8,
  'stop' => 1,
  'parity' => 0
));
dio_write($fd, 'AT\r\n');
$data = dio_read($fd, 256);
dio_close($fd);
echo $data;
?>

Ограничения

  • Не поддерживается на Windows – DIO доступен только *nix.
  • Устаревшее расширение – нет поддержки PHP 8, возможны сбои.

Вариант 4. Как использовать библиотеку symfony/serial-port?

Цель: совместимость с экосистемой Symfony. Случай: проекты на Symfony, требующие инъекции зависимостей.

Установка composer require symfony/serial-port и пример:

<?
use Symfony\Component\SerialPort\SerialPort;

$port = SerialPort::create('/dev/ttyUSB0', 9600);
$port->write('AT\r\n');
$response = $port->read(1024);
echo $response;
?>

Возможные трудности

  • Зависимости – библиотека требует php-process.
  • Отсутствие поддержки Windows – на Windows может потребоваться дополнительный адаптер.

Расширенные примеры работы с COM-портами

Пример 1: Чтение данных с цифрового термометра (DS18B20) через php-serial на Linux.

Пример
<?
require 'vendor/autoload.php';
use PhpSerial\Serial;

$serial = new Serial();
$serial->deviceSet('/dev/ttyUSB0');
$serial->confBaudRate(9600);
$serial->confParity('none');
$serial->confCharacterLength(8);
$serial->confStopBits(1);
$serial->deviceOpen();

// Отправка запроса температуры
$serial->sendMessage("TEMP\r\n");

// Ожидание ответа (200 мс)
$serial->readPort(); // пропустить эхо
usleep(200000);
$temperature = trim($serial->readPort());

echo "Температура: $temperature °C\n";

$serial->deviceClose();
?>
Температура: 23.45 °C

Пример 2: Отправка команды на Arduino с подтверждением через fopen и stty.

Пример
<?
$port = '/dev/ttyACM0';
shell_exec("stty -F $port 115200 cs8 -cstopb -parenb 2>&1");

$fp = fopen($port, 'w+');
if (!$fp) die('Не удалось открыть порт');

stream_set_blocking($fp, false);

// Команда включения светодиода
fwrite($fp, "LED_ON\n");

// Ждём 100 мс и читаем ответ
usleep(100000);
$answer = stream_get_contents($fp);
echo "Ответ: $answer\n";

fclose($fp);
?>
Ответ: OK

Пример 3: Работа с COM-портом на Windows через exec и mode.

Пример
<?
$port = 'COM4:';
// Настройка порта командой mode
$output = shell_exec("mode $port BAUD=9600 DATA=8 PARITY=N STOP=1 2>&1");
echo "Настройка: $output\n";

$fp = fopen($port, 'w+');
if (!$fp) die('Ошибка открытия COM-порта');

fwrite($fp, "*IDN?\r\n");
usleep(200000);
$idn = fread($fp, 100);
echo "Идентификация: $idn\n";

fclose($fp);
?>
Настройка: 
Идентификация: Keysight 34461A, MY57201234, A.02.08-02.40

Пример 4: Настройка паритета и стоп-битов с помощью DIO.

Пример
<?
$fd = dio_open('/dev/ttyS1', O_RDWR | O_NOCTTY);
if (!$fd) die('Не удалось открыть порт');

$conf = [
  'baud' => 19200,
  'bits' => 7,
  'stop'  => 2,
  'parity' => 1  // 1 - нечетность, 0 - четность
];
dio_tcsetattr($fd, $conf);

dio_write($fd, "DATA\n");
$reply = dio_read($fd, 50);
echo "Получено: $reply\n";

dio_close($fd);
?>
Получено: ACK

Пример 5: Неблокирующее чтение с помощью stream_select.

Пример
<?
$port = '/dev/ttyUSB0';
shell_exec("stty -F $port 9600 cs8 -cstopb -parenb");
$fp = fopen($port, 'r+');
stream_set_blocking($fp, false);

$read = [$fp];
$write = null;
$except = null;

// Отправить команду
fwrite($fp, "POLL\r\n");

// Ожидать данные до 1 секунды
if (stream_select($read, $write, $except, 1) > 0) {
    $data = stream_get_contents($fp);
    echo "Получены данные: $data\n";
} else {
    echo "Таймаут - нет данных\n";
}

fclose($fp);
?>
Получены данные: 123456

Работа с COM-портами в PHP - comments

En
Php com port (php)