PHP-controllable Linux hardware I/O extension built with Zephir.
LinuxSystem is a Zephir-based PHP extension that provides abstract controller base classes for common Linux hardware interfaces:
- GPIO (character device,
/dev/gpiochip*) - I2C (
/dev/i2c-*) - SPI (
/dev/spidev*) - PWM (sysfs,
/sys/class/pwm/pwmchip*)
You extend these base classes to create device-specific controllers in your own project.
LinuxSystem is written to be used standalone, but was designed to be paired with the ScrapyardIO framework, which builds higher-level device libraries on top of these low-level interfaces.
- DeptOfScrapyardRobotics (GitHub org):
https://github.com/DeptOfScrapyardRobotics - ScrapyardIO framework:
https://github.com/DeptOfScrapyardRobotics/ScrapyardIO
- Ecosystem (recommended)
- Requirements
- Tested hardware
- Clone
- Install (Linux devices)
- Verify installation
- Usage model (extending the base classes)
- Inheritance map
- API reference
- License
- Links
- Contributing
- PHP 8.3+
- Zephir 0.19+ (Zephir 19+)
- Install Zephir using the official instructions: Zephir installation guide
- Linux runtime environment (these APIs target Linux device nodes and sysfs)
LinuxSystem has been confirmed working on:
- Raspberry Pi 4
- Raspberry Pi 5
- Raspberry Pi Zero 2
- NVIDIA Jetson Orin
OrangePi, BananaPi, and other embedded Linux platforms have not been tested yet, but there’s no reason it shouldn’t work if the device has PHP 8.3+ and Zephir 0.19+ installed.
git clone https://github.com/DeptOfScrapyardRobotics/LinuxSystem.git
cd LinuxSystemThis repo includes an installer that builds and installs the extension.
chmod +x install.sh
sudo ./install.shWhat the installer does (high level):
- Runs
zephir fullcleanandzephir build(writes output tobuild.log) - Copies
ext/modules/linuxsystem.sointo your PHP extension directory - Creates
30-linuxsystem.iniin detectedconf.ddirectories to enable the extension - Verifies the extension loads in CLI
The compiled/loaded extension is named linuxsystem (this is what PHP will report).
php -m | grep -E '^linuxsystem$'
php --ri linuxsystemLinuxSystem ships abstract base classes. To implement a real device controller, you extend one of the base classes and implement:
register() -> void: set up defaults / validate configuration for your controllerdevicePath() -> string: return the Linux device path used by the controller- For PWM controllers:
channelPath() -> string(in addition todevicePath())
InterfaceController
├─ IOController
│ ├─ GeneralPurposeController (GPIO)
│ ├─ IntegratedCircuitController (I2C)
│ └─ SerialPeripheralController (SPI)
└─ PulseWidthModulator (PWM)
The tables below list what you inherit when you extend each base class.
File: linuxsystem/interfacecontroller.zep
| Visibility | Method | Signature | Notes |
|---|---|---|---|
| abstract public | register |
register() -> void |
Implement in your subclass to initialize defaults / validate configuration. |
| abstract public | devicePath |
devicePath() -> string |
Implement in your subclass to return the target device path. |
| public | basePath |
basePath(var path = null) -> string |
Getter/setter used by constructors to set base device path prefixes. |
| public | connected |
connected(var flag = null) -> bool |
Getter/setter for connection state (used by PWM controller logic). |
| public | online |
online(var flag = null) -> bool |
Getter/setter for online state (used by PWM controller logic). |
File: linuxsystem/iocontroller.zep
| Visibility | Method | Signature | Notes |
|---|---|---|---|
| public | open |
open() -> <IOController> |
Ensures the file descriptor is opened (lazy) and returns this. |
| public | close |
close() -> <IOController> |
Closes/clears the cached file descriptor and returns this. |
| public | fileDescriptor |
fileDescriptor(var fd = null) -> int |
Getter/setter. Lazily opens devicePath() if not already open. |
| public | clearFileDescriptor |
clearFileDescriptor() -> void |
Closes the cached fd (if any) and sets it to null. |
| public | _open |
_open(var device_path, int permissions = 2) -> int |
Low-level open(2) wrapper. Default permissions=2 (O_RDWR). |
| public | _close |
_close(int file_descriptor) -> int |
Low-level close(2) wrapper. |
| public | arrayToBytes |
arrayToBytes(array data) -> string |
Converts an array of ints (0-255) to a binary string. |
| public | bytesToArray |
bytesToArray(string bytes) -> array |
Converts a binary string into an array of byte values. |
File: linuxsystem/generalpurposecontroller.zep
| Visibility | Method | Signature | Notes |
|---|---|---|---|
| public | __construct |
__construct() |
Sets base path to /dev/gpiochip and calls register(). |
| public | chip |
chip(var c = null) -> int |
Getter/setter for chip index (typically maps to /dev/gpiochip{N}). |
| public | line |
line(var l = null, int dir = 0) -> int |
Getter/setter for the GPIO line offset. When set, claims the line. dir: 0 OUTPUT, 1 INPUT. |
| public | releaseLine |
releaseLine() -> void |
Releases the claimed line by closing the cached fd. |
| public | high |
high() -> bool |
Sets the claimed line HIGH via ioctl. |
| public | low |
low() -> bool |
Sets the claimed line LOW via ioctl. |
| public | read |
read() -> int |
Reads the claimed line value via ioctl. |
| protected | claimLine |
claimLine(int fd, int line, int direction = 0) -> int |
Claims a line via GPIO v2 ioctl and stores the returned line fd. |
| protected | getValue |
getValue(int fd) -> int |
Reads current line value using GPIO_V2_LINE_GET_VALUES_IOCTL. |
| protected | setHigh |
setHigh(int fd) -> bool |
Writes HIGH using GPIO_V2_LINE_SET_VALUES_IOCTL. |
| protected | setLow |
setLow(int fd) -> bool |
Writes LOW using GPIO_V2_LINE_SET_VALUES_IOCTL. |
File: linuxsystem/integratedcircuitcontroller.zep
| Visibility | Method | Signature | Notes |
|---|---|---|---|
| public | __construct |
__construct() |
Sets base path to /dev/i2c- and calls register(). |
| public | master |
master(var bus = null) -> var |
Getter/setter for the I2C master bus. Typically used to build devicePath(). |
| public | slave |
slave(var address = null) -> var |
Getter/setter for the slave address; when set, configures the fd with I2C_SLAVE. |
| protected | configSlave |
configSlave(int fd, int address) -> int |
Performs ioctl(fd, I2C_SLAVE, address). |
| protected | deviceRead |
deviceRead(int bytes_to_read) -> string |
Reads from the configured device and returns a binary string. |
| protected | deviceWrite |
deviceWrite(array payload) -> int |
Writes payload bytes to the device; returns number of bytes written. |
File: linuxsystem/serialperipheralcontroller.zep
| Visibility | Method | Signature | Notes |
|---|---|---|---|
| public | __construct |
__construct() |
Sets base path to /dev/spidev and calls register(). |
| public | master |
master(var bus = null) -> int |
Getter/setter for SPI bus. Typically used to build devicePath(). |
| public | chipSelect |
chipSelect(var cs = null) -> int |
Getter/setter for chip select. Typically used to build devicePath(). |
| public | spiMode |
spiMode(var value = null) -> var |
Getter/setter; when set, configures SPI_IOC_WR_MODE. |
| public | bitsPerWord |
bitsPerWord(var value = null) -> var |
Getter/setter; when set, configures SPI_IOC_WR_BITS_PER_WORD. |
| public | speed |
speed(var value = null) -> var |
Getter/setter; when set, configures SPI_IOC_WR_MAX_SPEED_HZ. |
| public | delay |
delay(var value = null) -> var |
Getter/setter for transfer delay (microseconds). |
| protected | deviceCall |
deviceCall(array payload, int bytes_to_read) -> string |
Performs an SPI transfer; returns the read buffer as a binary string. |
| protected | configSPIMode |
configSPIMode(int fd, int value) -> int |
Low-level ioctl wrapper for SPI mode. |
| protected | configBPW |
configBPW(int fd, int value) -> int |
Low-level ioctl wrapper for bits-per-word. |
| protected | configSpeed |
configSpeed(int fd, int value) -> int |
Low-level ioctl wrapper for max speed (Hz). |
| protected | setReadBit |
setReadBit(int byte) -> int |
Convenience helper: `byte |
File: linuxsystem/pulsewidthmodulator.zep
| Visibility | Method | Signature | Notes |
|---|---|---|---|
| public | __construct |
__construct() |
Sets base path to /sys/class/pwm/pwmchip and calls register(). |
| abstract public | channelPath |
channelPath() -> string |
Implement in your subclass to return the pwm channel path (typically .../pwm{N}). |
| public | chip |
chip(var c = null) -> int |
Selects pwm chip; updates connected/online depending on sysfs paths. |
| public | channel |
channel(var c = null) -> int |
Selects pwm channel; exports channel if needed; updates online. |
| public | export |
export() -> bool |
Writes the channel number to <devicePath>/export. |
| public | unexport |
unexport() -> bool |
Writes the channel number to <devicePath>/unexport. |
| public | period |
period(var value = null) -> int |
Getter/setter; when set, writes to <channelPath>/period. |
| public | getPeriod |
getPeriod() -> int |
Reads <channelPath>/period. |
| public | dutyCycle |
dutyCycle(var value = null) -> int |
Getter/setter; when set, writes to <channelPath>/duty_cycle. |
| public | getDutyCycle |
getDutyCycle() -> int |
Reads <channelPath>/duty_cycle. |
| public | enable |
enable(var value = null) -> int |
Getter/setter; when set, writes to <channelPath>/enable. |
| public | getEnable |
getEnable() -> int |
Reads <channelPath>/enable. |
| public | polarity |
polarity(var value = null) -> string |
Getter/setter; when set, writes to <channelPath>/polarity. |
| public | getPolarity |
getPolarity() -> string |
Reads <channelPath>/polarity. |
| protected | write |
write(string value, string to_file) -> bool |
Low-level sysfs write helper using stdio. |
| protected | read |
read(string file_path) -> string |
Low-level sysfs read helper using stdio (returns -1 on failure). |
LinuxSystem is licensed under the MIT License. See LICENSE.
- DeptOfScrapyardRobotics (GitHub org):
https://github.com/DeptOfScrapyardRobotics - ScrapyardIO framework:
https://github.com/DeptOfScrapyardRobotics/ScrapyardIO - Repository:
https://github.com/DeptOfScrapyardRobotics/LinuxSystem - Zephir install: Zephir installation guide
- Zephir docs:
https://docs.zephir-lang.com - PHP: php.net
Issues and pull requests are welcome.
- If you’re reporting a bug, include your OS, PHP version, Zephir version, and the relevant
build.logoutput. - If you’re adding features, keep the public API stable and document any new methods in this README.