hamzi/portflow
PortFlow connects serial hardware (thermal printers, barcode/RFID scanners, scales, IoT boards) to Laravel via a driver-based architecture. Parse raw bytes into typed events, queue routes, and printing workflows, with Web Serial API support for browser integration.
composer require hamzi/portflow
php artisan vendor:publish --tag=portflow-config
php artisan vendor:publish --tag=portflow-assets
<script src="{{ asset('vendor/portflow/portflow-serial.js') }}"></script>
<livewire:portflow-connector :baud-rate="115200" />
use Hamzi\PortFlow\Domain\Events\ProductScanned;
Event::listen(ProductScanned::class, fn(ProductScanned $event) => {
// Handle event
});
Thermal Printer Integration:
// Print a receipt
$bytes = PortFlow::print('receipts.order', ['order' => $order]);
PortFlow::encode('escpos', $bytes)->sendToPrinter();
portflow-serial.js → POST /portflow/ingestSerialDriver → SerialFrame → Event/Queue → Eloquent// Encode and send to hardware
PortFlow::encode('escpos', ['text' => 'Hello'])
->withDriver('thermal-printer')
->send();
config/portflow.php:
'mappings' => [
[
'driver' => 'raw-json',
'payload_field' => 'type',
'equals' => 'barcode.scan',
'event' => ProductScanned::class,
],
],
<livewire:portflow-status :device="device" />
PortFlow::routeToQueue(ProductScanned::class, $frame);
PortFlow::discoverDevices(['usbVendorId' => 6790]);
PortFlow::parse('raw-json', $chunk, ['device' => 'scale-a']);
$bytes = PortFlow::print('receipts.order', compact('order'));
Web Serial API Limitations:
autoConnectOnLoad with navigator.serial.getPorts() for remembered devices.Driver Configuration:
portflow-config) causes silent failures.php artisan vendor:publish --tag=portflow-config after install.Event Type Safety:
SerialEvent trigger warnings.SerialEvent for all custom events:
class WeightReceived implements SerialEvent { ... }
Buffer Overflows:
IoTFrameBuffer has a max_bytes limit (default: 16KB).PortFlow::setDebug(true); // Logs all frames to Laravel logs
PortFlow::parse('raw-json', $chunk)->dump();
@livewireScripts
<script>
window.addEventListener('esp32-browser-frame', (e) => {
console.log('Raw chunk:', e.detail);
});
</script>
Custom Drivers:
php artisan portflow:make-driver for scaffolding.SerialDriver for new protocols (e.g., Modbus).Blade Extensions:
// Add to AppServiceProvider
Blade::directive('escpos', function ($expr) {
return "<?php echo app('portflow')->print($expr); ?>";
});
Usage:
@escpos('receipts.order', ['order' => $order])
Queue Workers:
RouteSerialFrameJob for stuck frames:
php artisan queue:work --queue=portflow
config/portflow.php or per-request:
PortFlow::setDriver('rfid-ascii');
autoConnectOnLoad: true and filters to match remembered devices.csrfToken is passed to PortFlowBridge in JavaScript.PortFlow::batchFrames($frames)->routeToQueue(ProductScanned::class);
PortFlow::clearBuffer('raw-json');
wire:ignore for high-frequency updates:
<div wire:ignore>
<livewire:portflow-status />
</div>
How can I help you explore Laravel packages today?