microscrap / scrapyard-linux
Native Linux connection drivers for the ScrapyardIO GPIO framework: gpiod, i2c-dev, spidev, termios and sysfs PWM over ext-posi
Requires
- php: ^8.4|^8.5|^8.6
- ext-posi: ^0.10.0
- gpio/contracts: ^0.10.0
- gpio/digital: ^0.10.0
- gpio/i2c: ^0.10.0
- gpio/nuts-and-bolts: ^0.10.0
- gpio/pwm: ^0.10.0
- gpio/spi: ^0.10.0
- gpio/uart: ^0.10.0
- microscrap/gpio: ^0.10.0
- microscrap/i2c: ^0.10.0
- microscrap/spi: ^0.10.0
- microscrap/uart: ^0.10.0
- venusian-voyager/nuts-and-bolts: ^0.10.0
Requires (Dev)
- pestphp/pest: ^4
- venusian-voyager/config: ^0.10.0
- venusian-voyager/vessel: ^0.10.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-05 19:02:05 UTC
README
The Linux adapter for scrapyard-io/framework: the native driver for I2C, SPI, UART, digital pins and PWM, on a Raspberry Pi or any Linux board that exposes the standard kernel interfaces.
ext-posi 1:1 POSIX, ioctl, termios and gpiod calls
→ microscrap/{posix,gpio,i2c,spi,uart} PHP bindings
→ microscrap/scrapyard-linux the `native` driver per protocol ← this package
→ scrapyard-io/framework managers, transports, the event loop and via()
| Protocol | Kernel interface | connectTo() |
device() |
|---|---|---|---|
| I2C | /dev/i2c-N (i2c-dev) |
bus number | slave address |
| SPI | /dev/spidevN.CS (spidev) |
bus number | chip select |
| UART | a tty such as /dev/ttyAMA0 or /dev/ttyUSB0 |
device path | the same path |
| Digital | /dev/gpiochipN (libgpiod v2 character device) |
chip number | input() / output() line offset |
| PWM | /sys/class/pwm/pwmchipN (sysfs) |
chip number | channel |
Requirements
- PHP 8.4 or newer, on Linux
ext-posi0.10:pie install php-io-extensions/posiscrapyard-io/framework0.10, or just thegpio/*components it is split into- Access to the device nodes. On Raspberry Pi OS, add your user to
gpio,i2c,spianddialout, and enable the interfaces you use withraspi-configordtparam/dtoverlaylines inconfig.txt.
Installation
composer require microscrap/scrapyard-linux
The service provider is discovered automatically and registers native on every protocol manager. Make it the default in config/gpio.php, or name it at each call:
'protocols' => [ 'i2c' => ['default' => 'native'], 'spi' => ['default' => 'native'], 'uart' => ['default' => 'native'], 'digital-in' => ['default' => 'native'], 'pwm' => ['default' => 'native'], ],
Usage
use GeneralPurposeIO\Contracts\Digital\LineBias; $fan = app('gpio.i2c')->driver('native')->connectTo(1)->register()->device(1, 0x21); $temp = $fan->writeRead([0xFC], 1); $panel = app('gpio.spi')->driver('native')->connectTo(0)->speed(32_000_000)->register()->device(0, 0); $panel->write($frame); $gps = app('gpio.uart')->driver('native')->connectTo('/dev/ttyAMA0')->baud(9600)->register()->device('/dev/ttyAMA0'); $sentence = $gps->readUntil("\r\n", timeout_ms: 1000); $pins = app('gpio.digital')->driver('native')->connectTo(0)->register(); $button = $pins->input(0, 27, LineBias::PULL_UP); $edge = $button->listen(500, rising_events: false, falling_events: true); $servo = app('gpio.pwm')->driver('native')->connectTo(0)->register()->device(0, 0); $servo->setPeriod(20_000_000); $servo->setDutyCycle(1_500_000); $servo->setEnable(true);
The framework's README covers the transports, the event loop and via(). What this adapter adds:
Digital pins
- Each pin is its own line request. Inputs always request both edges; the framework filters rising or falling.
- The kernel keeps 64 unread edges per pin, the same depth as the framework's queue. Edge timestamps and sequence numbers are the kernel's.
- A watched input puts its line-request fd on the event loop, so edges wake the loop directly.
connectTo(N)->consumer('my-app')sets the consumer name tools likegpioinfoshow (defaultscrapyard-io-digital-io).
I2C
- Every slave on a bus shares one fd, and each call selects its address first.
writeRead()is a singleI2C_RDWRtransfer with a repeated START.bulkWrite()sends each chunk as its own message, 42 messages per transfer.
SPI
- Each chip select opens its own
/dev/spidevN.CSwith the connection's mode, speed and word size (bitsPerByte(), default 8). - A call longer than spidev's buffer (
/sys/module/spidev/parameters/bufsiz, 4096 by default) goes out as several messages with chip select held throughout, so it is still one selection on the wire. - Every process on the machine takes the bus in turn through
flock()on/run/lock/scrapyard-spi<bus>.lock. A pool worker, another program and this process never interleave on one bus. speed($hz)on a slave sets its own clock, carried on every transfer.- LSB-first on a controller that refuses it, such as the Pi 5's, is done by reversing bits in PHP for 8-bit words. Other word sizes throw.
writeFrom([[$address, $length], …])sends bytes straight out of memory, such as an ext-fb framebuffer's rows, with no copy into PHP. Surface'sDirectEDisplayuses it to drive an ST77xx panel.
UART
- Ports are configured raw with never-waiting reads (VMIN=0, VTIME=0).
- Blocking reads and writes wait in
ppoll()with the caller's timeout. On the event loop, the tty fd wakes the loop. - A USB serial device that is unplugged while open throws on the next read instead of spinning.
dtr()andrts()drive the modem lines; a port without them throws.
PWM
- PWM uses plain sysfs files and needs no extension.
- A channel is exported the first time
device()asks for it. The driver waits up to 500 ms for udev to make its files writable; set a different limit withconnectTo(N)->readyTimeout($ms). With an event loop bound, that wait keeps the loop turning. - Every setter returns the value read back from sysfs.
close()disables the channel and unexports it.
Offloading
via()jobs run on the app's worker pools:'thread'or'process', or with none named, the thread pool when it is on and the process pool otherwise.- A pool worker opens its own bus: every fd here is close-on-exec, so nothing is inherited.
- PWM workers are built on the same sysfs root as the parent's driver.
Testing
composer install vendor/bin/pest
The suite needs Linux and ext-posi, and no hardware:
- a pseudo-terminal and a FIFO stand in for serial devices;
- temporary directories stand in for sysfs;
- the planned spidev messages are checked without a device.
tests/SPI/PiSpiBusTest.php runs on a Raspberry Pi 5 with spi0 and spi10 enabled and nothing on CE1. Anywhere else it skips itself.
License
MIT. See LICENSE.