Идем на сайт php.net, выбираем Windows downloads и скачиваем Zip релиз. На момент написания статьи 8.2.1.
Windows
Распаковываем скачанный архив командой
mkdir php-8.2.1 && tar -x -f php-8.2.1-nts-Win32-vs16-x64.zip -C php-8.2.1
Переходим в распакованную директорию
cd 8.2.1
Создайте php.ini для конфигурации из готового шаблона
copy php.ini-development php.ini
Откройте php.ini через текстовый редактор и раскомментируйте следующие строки:
extension_dir = "ext"
extension=ffi
Измените значение переменной short_open_tag=Off на On
Добавьте в переменную окружения PATH путь к папке php-8.2.1.
Подключение RISDK к приложению на PHP
С помощью расширения FFI подключаем распределенную библиотеку risdk, для этого нам в конструкторе cdef нужно указать пути к файлам .so или .dll и .h
Создадим php файл
type nul > shared_client.php
Linux
Идем на сайт php.net и скачиваем оттуда последний релиз. На момент написания статьи 8.2.1.
Скачанный архив можно распаковать командой
tar -xzf php-8.2.1.tar.gz
Переходим в распакованную директорию
cd php-8.2.1
Следующим шагом перед установкой выполняем конфигурацию gcc с указанием расширений которые мы хотим использовать в дальнейшем.
Здесь перечислены часто используемые расширения. Главное расширение, которое обязательно должно быть это FFI (флаг --with-ffi).
./configure --with-openssl --enable-mbstring --with-curl --enable-calendar --enable-bcmath --enable-exif
--enable-gd --enable-ftp --enable-pcntl --enable-sockets --with-sodium --with-zip --with-pear --enable-mysqlnd
--with-pdo-mysql --with-pgsql=/usr/bin/pg_config --with-pdo-pgsql=/usr/bin/pg_config
--with-config-file-scan-dir=/etc/php/8.0 --with-config-file-path=/etc/php/8.0/cli/php.ini --with-zlib --with-ffi
Затем проверяем во сколько потоков может происходить компиляция на вашей машине
nproc
Для более быстрой компиляции вызываем команду make с флагом -j и указываем кол-во потоков сразу после флага. К примеру у вас на машине 12 потоков.
make -j12
Выполняем скрипт установки
sudo make install
После завершения установки проверяем результат командой
php -v
Создадим php файл
touch shared_client.php
Пример подключения библиотеки, инициализации SDK и создания компонента
Структура проекта
. workspace/
--- > shared_client.php
--- > librisdk.h
--- > librisdk.so/.dll
--- > libusb-1.0.so/.dll
Подключим библиотеку
Обратите внимание на значение переменной $RELATIVE_PATH. Это относительный путь для поиска библиотеки, если она лежит ниже чем php скрипт.
Например при значении '/..' поиск библиотеки будет выполняться на одну директорию ниже.
При значении '' файлы librisdk.h и librisdk.so должны располагаться в той же директории что и php скрипт.
<?
$RELATIVE_PATH = '';
$headers = file_get_contents(__DIR__ . $RELATIVE_PATH . '/librisdk.h');
// Вырежем лишнии заголовки
$headers = preg_replace(['/#ifdef __cplusplus\s*extern "C" {\s*#endif/i', '/#ifdef __cplusplus\s*}\s*#endif/i'], '', $headers);
$ffi = FFI::cdef($headers, __DIR__ . $RELATIVE_PATH . '/librisdk.so');
print("Success \n");
?>
Для запуска скрипта выполним в терминале следующую команду:
php shared_client.php
Создадим компонент
$logLevel = 2;
$errorText = $ffi->new('char[1000]', 0); // Выделяем память на строку с ошибкой
// Инициализируем SDK
$errCode = $ffi->RI_SDK_InitSDK($logLevel, $errorText);
if ($errCode) {
// Выведем текст и код ошибки в терминал, при возникновении
print("RI_SDK_InitSDK errorText:" . FFI::string($errorText) . " errCode: " . $errCode . " \n");
return $errCode;
}
// Создаем базовый компонент
$descriptor = $ffi->new('int', 0); // Выделяем память на переменную с номером дескриптора
$errCode = $ffi->RI_SDK_CreateBasic(FFI::addr($descriptor), $errorText);
if ($errCode) {
// Выведем текст и код ошибки в терминал, при возникновении
print("RI_SDK_CreateBasic errorText:" . FFI::string($errorText). " errCode: " . $errCode . " \n");
return $errCode;
}
// Выведем полученный дескриптор компонента
print('descriptor: ' . + $descriptor->cdata . " \n");
Запустим скрипт
php shared_client.php
Результат успешной работы должен выглядеть следующим образом:
descriptor: 1
Success
Пример программы на PHP использующий RI_SDK
Описание
Данная программа является примером, использования библиотеки. Она реализует простой сценарий поведения робота. Сперва все сервоприводы встают в условное стартовое положение. После робот берет кубик с позиции слева и перемещает его на позицию справа. После сервоприводы возвращаются в первоначальное положение.
Одновременно с движением робота, происходит свечение светодиода. Светодиод горит красным цветом пока сервоприводы приводятся к стартовому положению, после загорается зеленым. Далее светодиоды моргает зеленым при передвижении тела робота и мерцает синим, пока робот кладет/берет кубик.
Структура проекта
. workspace/
--- > demo.php
--- > librisdk.h
--- > librisdk.dll/.so
--- > CH341DLL.DLL/ CH341DLLA64.DLL
--- > SLABHIDDevice.dll
--- > SLABHIDtoSMBus.dll
--- > libusb-1.0.dll/.so
Реализация
<?
$GLOBALS = ['body_start_pulse' => 1500, // стартовая позиция тела
'arrowR_start_pulse' => 2000, // стартовая возиция правой стрелы
'arrowL_start_pulse' => 1000, // стартовая позиция левой стрелы
'claw_start_pulse' => 1000, // стартовая позиция клешни
'claw_rotate_start_pulse' => 1500, // стартовая позиция поворота клешни
'arrowR_over_cube_position' => -30, // позиция правой стрелы над кубиком
'arrowL_over_cube_position' => -10, // позиция левой стрелы над кубиком
'claw_unclenched_position' => 80, // позиция открытой клешни
'arrowR_cube_position' => -75, // позиция правой стрелы на месте кубика
'arrowL_cube_position' => 55, // позиция левой стрелы на месте кубика
];
// структура дескрипторов устройства
class device
{
public $i2c; // дескриптор i2c
public $pwm; // дескриптор pwm
public $body; // дескриптор сервопривода тела
public $claw; // дескриптор сервопривода клешни
public $arrowR; // дескриптор сервопривода правой стрелы
public $arrowL; // дескриптор сервопривода левой стрелы
public $clawRotate; // дескриптор сервопривода поворота клешни
public $led; // дескриптор светодиода
public function __construct($i2c, $pwm, $body, $claw, $arrowR, $arrowL, $clawRotate, $led)
{
$this->i2c = $i2c;
$this->pwm = $pwm;
$this->body = $body;
$this->claw = $claw;
$this->arrowR = $arrowR;
$this->arrowL = $arrowL;
$this->clawRotate = $clawRotate;
$this->led = $led;
}
}
// для хранения дескриптоов сервоприводов
class servo
{
public $descriptor; // дескриптор сервопривода
public $startPosition; // стартовая позиция сервопривода
public function __construct($descriptor, $startPosition)
{
$this->descriptor = $descriptor;
$this->startPosition = $startPosition;
}
}
// Обратите внимание на значение переменной $RELATIVE_PATH
// Это относительный путь для поиска библиотеки, если она лежит ниже чем php скрипт
// Например при значении '/..' поиск библиотеки будет выполняться на одну директорию ниже
// При значении '' файлы librisdk.h и librisdk.so должны располагаться в той же директории что и php скрипт
$RELATIVE_PATH = '';
$headers = file_get_contents(__DIR__ . $RELATIVE_PATH . '/librisdk.h');
// Вырежем лишнии заголовки
$headers = preg_replace(['/#ifdef __cplusplus\s*extern "C" {\s*#endif/i', '/#ifdef __cplusplus\s*}\s*#endif/i'], '', $headers);
$ffi = FFI::cdef($headers, __DIR__ . $RELATIVE_PATH . '/librisdk.dll');
$errorText = $ffi->new('char[1000]', 0); // Выделяем память на строку с ошибкой. Передается как входной параметр,при возникновении ошибки в эту переменную будет записан текст ошибки
// инициализация структуры устройства
$robohand = new Device($ffi->new('int', 0), $ffi->new('int', 0), $ffi->new('int', 0), $ffi->new('int', 0), $ffi->new('int', 0),
$ffi->new('int', 0), $ffi->new('int', 0), $ffi->new('int', 0));
// массив дескрипторов сервоприводов
$servos = [
new servo($robohand->body, $GLOBALS['body_start_pulse']),
new servo($robohand->claw, $GLOBALS['claw_start_pulse']),
new servo($robohand->arrowR, $GLOBALS['arrowR_start_pulse']),
new servo($robohand->arrowL, $GLOBALS['arrowL_start_pulse']),
new servo($robohand->clawRotate, $GLOBALS['claw_rotate_start_pulse'])
];
$errCode = start($ffi, $servos, $robohand, $errorText);
if ($errCode) {
print("ErrorText: " . FFI::string($errorText) . " ErrCode: " . $errCode . " \n");
}
// start - запускает демо программу
function start($ffi, $servos, $robohand, $errorText)
{
$speed = 100; // скорость в градусах в секунду
// initDevice - инициализация библиотеки и устройств
$errCode = initDevice($ffi, $servos, $robohand, $errorText);
if ($errCode) {
return $errCode;
}
// startPositionAllServo - переводит все сервоприводы в стартовую позицию
$errCode = startPositionAllServo($ffi, $robohand, $servos, $errorText);
if ($errCode) {
return $errCode;
}
// Двигаем тело к местонахождению кубика
$errCode = rotateBody($ffi, $robohand, 45, $speed, $errorText);
if ($errCode) {
return $errCode;
}
// Берем кубик
$errCode = get($ffi, $robohand, $speed, $errorText);
if ($errCode) {
return $errCode;
}
//Двигаем тело к новому местонахождению кубика
$errCode = rotateBody($ffi, $robohand, -90, $speed, $errorText);
if ($errCode) {
return $errCode;
}
//Кладем кубик
$errCode = put($ffi, $robohand, $speed, $errorText);
if ($errCode) {
return $errCode;
}
// Возвращаем тело в стартовое положение
$errCode = rotateBody($ffi, $robohand, 45, $speed, $errorText);
if ($errCode) {
return $errCode;
}
// Уничтожаем компоненты и библиотеку
$errCode = destruct($ffi, $robohand, $servos, $errorText);
if ($errCode) {
return $errCode;
}
print("Success \n");
return 0;
}
// get - берет кубик
function get($ffi, $robohand, $speed, $errorText)
{
//выполняем мерцание светодиодом,передаем дескриптор светодиода,3 параметра цвета(RGB),продолжительность,кол-во повторений и включаем асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_RGB_LED_Flicker($robohand->led->cdata, 0, 0, 255, 500, 0, true, $errorText);
if ($errCode) {
return $errCode;
}
//выполняем поворот на заданный угол,передаем дескриптор стрелы,угол,скорость и асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_ServoDrive_Turn($robohand->arrowR->cdata, $GLOBALS['arrowR_over_cube_position'], $speed, false, $errorText);
if ($errCode) {
return $errCode;
}
//выполняем поворот на заданный угол,передаем дескриптор стрелы,угол,скорость и асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_ServoDrive_Turn($robohand->arrowL->cdata, $GLOBALS['arrowL_over_cube_position'], $speed, false, $errorText);
if ($errCode) {
return $errCode;
}
//выполняем поворот на заданный угол,передаем дескриптор клешни,угол,скорость и асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_ServoDrive_Turn($robohand->claw->cdata, $GLOBALS['claw_unclenched_position'], $speed, false, $errorText);
if ($errCode) {
return $errCode;
}
//выполняем поворот на заданный угол,передаем дескриптор стрелы,угол,скорость и асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_ServoDrive_Turn($robohand->arrowR->cdata, ($GLOBALS['arrowR_cube_position'] - $GLOBALS['arrowR_over_cube_position']), $speed, false, $errorText);
if ($errCode) {
return $errCode;
}
//выполняем поворот на заданный угол,передаем дескриптор стрелы,угол,скорость и асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_ServoDrive_Turn($robohand->arrowL->cdata, ($GLOBALS['arrowL_cube_position'] - $GLOBALS['arrowL_over_cube_position']), $speed, false, $errorText);
if ($errCode) {
return $errCode;
}
//выполняем поворот на заданный угол,передаем дескриптор клешни,угол,скорость и асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_ServoDrive_Turn($robohand->claw->cdata, (-1) * $GLOBALS['claw_unclenched_position'], $speed, false, $errorText);
if ($errCode) {
return $errCode;
}
//выполняем поворот на заданный угол,передаем дескриптор стрелы,угол,скорость и асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_ServoDrive_Turn($robohand->arrowR->cdata, (-1) * $GLOBALS['arrowR_cube_position'], $speed, false, $errorText);
if ($errCode) {
return $errCode;
}
//выполняем поворот на заданный угол,передаем дескриптор стрелы,угол,скорость и асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_ServoDrive_Turn($robohand->arrowL->cdata, (-1) * $GLOBALS['arrowL_cube_position'], $speed, false, $errorText);
if ($errCode) {
return $errCode;
}
return 0;
}
// rotateBody - вращение тела в указанный угол
function rotateBody($ffi, $robohand, $angle, $speed, $errorText)
{
//выполняем мигание с заданной частотой,передаем дескриптор светодиода,3 параметра цвета(RGB),частоту,продолжительность и включаем асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_RGB_LED_FlashingWithFrequency($robohand->led->cdata, 0, 255, 0, 5, 0, true, $errorText);
if ($errCode) {
return $errCode;
}
//выполняем поворот на заданный угол,передаем дескриптор тела,угол,скорость и асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_ServoDrive_Turn($robohand->body->cdata, $angle, $speed, false, $errorText);
if ($errCode) {
return $errCode;
}
//выполняем поворот на заданный угол,передаем дескриптор клешни,угол,скорость и асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_ServoDrive_Turn($robohand->clawRotate->cdata, 45, $speed, false, $errorText);
if ($errCode) {
return $errCode;
}
//выполняем поворот на заданный угол,передаем дескриптор клешни,угол,скорость и асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_ServoDrive_Turn($robohand->clawRotate->cdata, -45, $speed, false, $errorText);
if ($errCode) {
return $errCode;
}
return 0;
}
// initServos - создает сервоприводы и линкует их
function initServos($ffi, $robohand, $servos, $errorText)
{
//создаем 5 сервоприводов и линкуем их к пинам 0-4
for ($i = 0; $i < 5; $i++) {
// создаем компонент сервопривода с конкретной моделью как исполняемое устройство и получаем дескриптор сервопривода
$errCode = $ffi->RI_SDK_CreateModelComponent("executor", "servodrive", "mg90s", FFI::addr($servos[$i]->descriptor), $errorText);
if ($errCode) {
return $errCode;
}
//связываем сервопривод с ШИМ,передаем дескриптор сервопривода и шим
$errCode = $ffi->RI_SDK_LinkServodriveToController($servos[$i]->descriptor->cdata, $robohand->pwm->cdata, $i, $errorText);
if ($errCode) {
return $errCode;
}
}
return 0;
}
// initDevice - инициализация библиотеки и устройств
function initDevice($ffi, $servos, $robohand, $errorText)
{
$logLevel = 2; // уровень логирования
// Инициализируем SDK
$errCode = $ffi->RI_SDK_InitSDK($logLevel, $errorText);
if ($errCode) {
return $errCode;
}
// создаем компонент ШИМ с конкретной моделью как исполняемое устройство,получаем дескриптор сервопривода
$errCode = $ffi->RI_SDK_CreateModelComponent("connector", "pwm", "pca9685", FFI::addr($robohand->pwm), $errorText);
if ($errCode) {
return $errCode;
}
// создаем компонент i2c адатера
// Здесь осуществлен примитивное определение подключенной модели адаптера
// Сначала пробуем создать i2c адаптер модели ch341 и связать с ним ШИМ
$errCode = $ffi->RI_SDK_CreateModelComponent("connector", "i2c_adapter", "ch341", FFI::addr($robohand->i2c), $errorText);
if ($errCode) {
return $errCode;
}
//связываем i2c адаптер с ШИМ по адресу 0x40
$errCode = $ffi->RI_SDK_LinkPWMToController($robohand->pwm->cdata, $robohand->i2c->cdata, 0x40, $errorText);
if ($errCode) {
// Если не получается то пробуем создать i2c адаптер модели cp2112
$errCode = $ffi->RI_SDK_CreateModelComponent("connector", "i2c_adapter", "cp2112", FFI::addr($robohand->i2c), $errorText);
if ($errCode) {
return $errCode;
}
$errCode = $ffi->RI_SDK_LinkPWMToController($robohand->pwm->cdata, $robohand->i2c->cdata, 0x40, $errorText);
if ($errCode) {
return $errCode;
}
}
// создаем компонент светодиода с конкретной моделью (ky016) как исполняемое устройство и получаем дескриптор светодиода
$errCode = $ffi->RI_SDK_CreateModelComponent("executor", "led", "ky016", FFI::addr($robohand->led), $errorText);
if ($errCode) {
return $errCode;
}
//связываем светодиод адаптер с ШИМ,передаем значения трех пинов к которым подключен светодиод
$errCode = $ffi->RI_SDK_LinkLedToController($robohand->led->cdata, $robohand->pwm->cdata, 15, 14, 13, $errorText);
if ($errCode) {
return $errCode;
}
//инициализируем сервоприводы
$errCode = initServos($ffi, $robohand, $servos, $errorText);
if ($errCode) {
return $errCode;
}
return 0;
}
// startPositionAllServo - переводит все сервоприводы в стартовую позицию
function startPositionAllServo($ffi, $robohand, $servos, $errorText)
{
//выполняем одиночное свечение светодиодом,передаем дескриптор светодиода,3 параметра цвета(RGB), и включаем асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_RGB_LED_SinglePulse($robohand->led->cdata, 255, 0, 0, 0, true, $errorText);
if ($errCode) {
return $errCode;
}
//приводим сервоприводы в стартовое положение
for ($i = 0; $i < 5; $i++) {
//выполняем поворот сервопривода в заданный угол,передаем дескриптор сервоприовда,значение угла
$errCode = $ffi->RI_SDK_exec_ServoDrive_TurnByPulse($servos[$i]->descriptor->cdata, $servos[$i]->startPosition, $errorText);
if ($errCode) {
return $errCode;
}
sleep(0.5);
}
//выполняем одиночное свечение светодиодом,передаем дескриптор светодиода,3 параметра цвета(RGB), и включаем асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_RGB_LED_SinglePulse($robohand->led->cdata, 0, 255, 0, 0, true, $errorText);
if ($errCode) {
return $errCode;
}
sleep(0.5);
return 0;
}
// put - кладет кубик
function put($ffi, $robohand, $speed, $errorText)
{
//выполняем мерцание светодиодом,передаем дескриптор светодиода,3 параметра цвета(RGB),продолжительность,кол-во повторений и включаем асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_RGB_LED_Flicker($robohand->led->cdata, 0, 0, 255, 500, 0, true, $errorText);
if ($errCode) {
return $errCode;
}
//выполняем поворот на заданный угол,передаем дескриптор стрелы,угол,скорость и асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_ServoDrive_Turn($robohand->arrowR->cdata, $GLOBALS['arrowR_cube_position'], $speed, false, $errorText);
if ($errCode) {
return $errCode;
}
//выполняем поворот на заданный угол,передаем дескриптор стрелы,угол,скорость и асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_ServoDrive_Turn($robohand->arrowL->cdata, $GLOBALS['arrowL_cube_position'], $speed, false, $errorText);
if ($errCode) {
return $errCode;
}
//выполняем поворот на заданный угол,передаем дескриптор клешни,угол,скорость и асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_ServoDrive_Turn($robohand->claw->cdata, $GLOBALS['claw_unclenched_position'], $speed, false, $errorText);
if ($errCode) {
return $errCode;
}
//выполняем поворот на заданный угол,передаем дескриптор стрелы,угол,скорость и асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_ServoDrive_Turn($robohand->arrowR->cdata, (-1) * $GLOBALS['arrowR_cube_position'], $speed, false, $errorText);
if ($errCode) {
return $errCode;
}
//выполняем поворот на заданный угол,передаем дескриптор стрелы,угол,скорость и асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_ServoDrive_Turn($robohand->arrowL->cdata, (-1) * $GLOBALS['arrowL_cube_position'], $speed, false, $errorText);
if ($errCode) {
return $errCode;
}
//выполняем поворот на заданный угол,передаем дескриптор клешни,угол,скорость и асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_ServoDrive_Turn($robohand->claw->cdata, (-1) * $GLOBALS['claw_unclenched_position'], $speed, false, $errorText);
if ($errCode) {
return $errCode;
}
return 0;
}
// destructServos - уничтожает сервоприводы
function destructServos($ffi, $servos, $errorText)
{
//уничтожаем сервоприводы
for ($i = 0; $i < 5; $i++) {
$errCode = $ffi->RI_SDK_DestroyComponent($servos[$i]->descriptor->cdata, $errorText);
if ($errCode) {
return $errCode;
}
}
return 0;
}
// destruct - уничтожает все компоненты и библиотеку
function destruct($ffi, $robohand, $servos, $errorText)
{
//выполняем одиночное свечение светодиодом,передаем дескриптор светодиода,3 параметра цвета(RGB), и включаем асинхронный режим работы
$errCode = $ffi->RI_SDK_exec_RGB_LED_SinglePulse($robohand->led->cdata, 255, 0, 0, 0, true, $errorText);
if ($errCode) {
return $errCode;
}
//уничтожаем сервоприводы
$errCode = destructServos($ffi, $servos, $errorText);
if ($errCode) {
return $errCode;
}
//останавливаем свечение светодиода с определенным дескриптором
$errCode = $ffi->RI_SDK_exec_RGB_LED_Stop($robohand->led->cdata, $errorText);
if ($errCode) {
return $errCode;
}
//удаляем компонент светодиода по дескриптору
$errCode = $ffi->RI_SDK_DestroyComponent($robohand->led->cdata, $errorText);
if ($errCode) {
return $errCode;
}
//сбрасываем все порты на ШИМ
$errCode = $ffi->RI_SDK_sigmod_PWM_ResetAll($robohand->pwm->cdata, $errorText);
if ($errCode) {
return $errCode;
}
// удаляем компонент ШИМ
$errCode = $ffi->RI_SDK_DestroyComponent($robohand->pwm->cdata, $errorText);
if ($errCode) {
return $errCode;
}
//удаляем компонент i2c
$errCode = $ffi->RI_SDK_DestroyComponent($robohand->i2c->cdata, $errorText);
if ($errCode) {
return $errCode;
}
//удаляем sdk со всеми компонентами в реестре компонентов
$errCode = $ffi->RI_SDK_DestroySDK(true, $errorText);
if ($errCode) {
return $errCode;
}
return 0;
}