PHPackages                             orderlyconnect/astm-e1394-91 - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Parsing &amp; Serialization](/categories/parsing)
4. /
5. orderlyconnect/astm-e1394-91

ActiveLibrary[Parsing &amp; Serialization](/categories/parsing)

orderlyconnect/astm-e1394-91
============================

Parse, build and transmit ASTM E1394-97 clinical laboratory messages over TCP, serial or files — including full LLP framing, ENQ/ACK handshake, and a fluent message builder.

v1.0.0(2mo ago)02↓100%MITPHPPHP ^8.1

Since Apr 7Pushed 2mo agoCompare

[ Source](https://github.com/OrderlyConnect/astm-e1394-91)[ Packagist](https://packagist.org/packages/orderlyconnect/astm-e1394-91)[ Docs](https://github.com/OrderlyConnect/astm-e1394-91)[ RSS](/packages/orderlyconnect-astm-e1394-91/feed)WikiDiscussions master Synced 1w ago

READMEChangelog (1)Dependencies (3)Versions (2)Used By (0)

OrderlyConnect ASTM E1394-97
============================

[](#orderlyconnect-astm-e1394-97)

[![Tests](https://github.com/OrderlyConnect/astm-e1394-91/actions/workflows/tests.yml/badge.svg)](https://github.com/OrderlyConnect/astm-e1394-91/actions/workflows/tests.yml)[![Latest Version on Packagist](https://camo.githubusercontent.com/67a3b583574363442e294f7e9cbd43e80acf98581337538480d28a9f46c6ac9b/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6f726465726c79636f6e6e6563742f6173746d2d65313339342d39312e737667)](https://packagist.org/packages/orderlyconnect/astm-e1394-91)[![PHP Version](https://camo.githubusercontent.com/054356ee1b692df7e72b575d457eab241a0ba23f77546ddaeda6759a8b785ab8/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f6f726465726c79636f6e6e6563742f6173746d2d65313339342d39312e737667)](https://packagist.org/packages/orderlyconnect/astm-e1394-91)[![License](https://camo.githubusercontent.com/b5459df8cda242ce1d0b09f199fb49613b49be3c5ec4b1373bf8f3b3347d4678/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f4f726465726c79436f6e6e6563742f6173746d2d65313339342d39312e737667)](LICENSE)

A complete, production-ready PHP 8.1+ library for **parsing**, **building**, and **transmitting** ASTM E1394-97 messages — the standard used by clinical laboratory instruments (haematology analysers, chemistry analysers, coagulation systems, etc.) to exchange patient results with LIS/HIS systems.

Validated against real Sysmex XN-350 haematology analyser output.

---

Features
--------

[](#features)

- **Parse** plain-text and LLP-framed ASTM messages into typed PHP objects
- **Build** messages with a fluent, type-safe builder API
- **Send** over TCP, RS-232 serial, or files with full ENQ/ACK/EOT handshake
- **Receive** inbound instrument connections with a byte-level state machine receiver
- **TCP Server** — accept multiple instrument connections in a dedicated process
- **LLP framing** — ASTM E1381-95 checksum, ETX/ETB frame splitting, streaming decoder
- **EscapeCodec** — encode/decode `&F& &R& &S& &E&` sequences (§6.6)
- **MessageValidator** — structural rule checking
- **MessageCollection** — batch processing with cross-session queries
- **MessageDiff** — field-by-field comparison for correction workflows
- **JSON/array serialisation** — `toArray()` / `toJson()`
- Zero runtime dependencies

---

Installation
------------

[](#installation)

```
composer require orderlyconnect/astm-e1394-91
```

**Requirements:** PHP 8.1+.
Serial port support requires `stty` (Linux/macOS) or `mode.com` (Windows).

---

Quick Start
-----------

[](#quick-start)

```
use Astm\Astm;

// ── Parse ──────────────────────────────────────────────────────────────────
$message = Astm::parse(file_get_contents('/tmp/result.astm'));

foreach ($message->getResultMap() as $test => $data) {
    printf("%-10s  %6s %-10s  flag=%s\n",
        $test, $data['value'], $data['units'], $data['flag']);
}

// ── Build ──────────────────────────────────────────────────────────────────
$message = Astm::build()
    ->sender('MY-LIS', 'v2.0')
    ->patient(fn ($p) => $p->id('PAT-001')->name('Okafor', 'Ngozi')->sex('F'))
    ->order(fn ($o) => $o->specimenId('EDTA-001')->addTest('WBC')->addTest('HGB'))
    ->result(fn ($r) => $r
        ->test('WBC')->value('7.2')->units('10*3/uL')
        ->referenceRangeFromBounds(4.0, 11.0)->flag('N'))
    ->result(fn ($r) => $r
        ->test('HGB')->value('14.1')->units('g/dL')->flag('N'))
    ->build();

// ── Send over TCP ──────────────────────────────────────────────────────────
Astm::sendTcp($message, '192.168.1.50', port: 3001);

// ── Receive (blocking server) ──────────────────────────────────────────────
Astm::listen(function (\Astm\Message $msg, string $from): void {
    echo "[{$from}] " . count($msg->getResults()) . " results\n";
    file_put_contents('/var/log/astm/' . uniqid() . '.json', $msg->toJson());
}, host: '0.0.0.0', port: 3001);
```

---

Table of Contents
-----------------

[](#table-of-contents)

- [Parsing](#parsing)
- [Building Messages](#building-messages)
- [Cloning &amp; Modifying](#cloning--modifying)
- [Sending](#sending)
- [Receiving](#receiving)
- [TCP Server](#tcp-server)
- [Transports](#transports)
- [LLP Protocol](#llp-protocol)
- [Utility Classes](#utility-classes)
- [Serialisation](#serialisation)
- [ASTM Record Reference](#astm-record-reference)
- [Examples](#examples)
- [Testing](#testing)
- [Extending](#extending)
- [Publishing to Packagist](#publishing-to-packagist)

---

Parsing
-------

[](#parsing)

```
// Single message from a string
$message = Astm::parse($rawString);

// All sessions from a plain-text file → MessageCollection
$collection = Astm::parseFile('/path/to/session.astm');

// Decode an LLP-framed binary blob
$collection = Astm::decodeLlp(file_get_contents('/captures/session.bin'));

// Message accessors
$message->getHeader();            // ?Header
$message->getFirstPatient();      // ?Patient
$message->getFirstOrder();        // ?Order
$message->getResults();           // Result[]
$message->getAbnormalResults();   // Result[]  — flag ≠ '' and ≠ 'N'
$message->getFinalResults();      // Result[]  — status = 'F'
$message->hasAbnormalities();     // bool
$message->getComments();          // Comment[]
$message->getTerminator();        // ?Terminator
$message->getRecordsByType('R');  // AbstractRecord[]

// Result map — keyed by test name
// ['WBC' => ['value'=>'7.2','units'=>'10*3/uL','flag'=>'N','status'=>'F'], ...]
$message->getResultMap();
```

---

Building Messages
-----------------

[](#building-messages)

```
use Astm\Astm;
use Astm\Records\Result;

$message = Astm::build()
    ->sender('MY-LIS', 'v2.0', processingId: 'P', receiverId: 'LAB-SYSTEM')
    ->patient(fn ($p) => $p
        ->id('PAT-001', 'LAB-001')
        ->name('Smith', 'John', 'A')      // last, first, middle
        ->sex('M')                         // M / F / U
        ->birthdate('19850310')            // YYYYMMDD
        ->address('123 Main St')
        ->phone('555-1234')
        ->physician('DR-JONES'))
    ->order(fn ($o) => $o
        ->specimenId('EDTA-001')
        ->addTest('WBC')->addTest('HGB')->addTest('PLT')
        ->collectionDateTime('20250329143000')
        ->priority('R')                    // R=routine, S=stat
        ->reportType('F'))
    ->result(fn ($r) => $r
        ->test('WBC')
        ->value('7.2')
        ->units('10*3/uL')
        ->referenceRangeFromBounds(4.0, 11.0)   // produces "4-11"
        ->flag(Result::FLAG_NORMAL)
        ->status(Result::STATUS_FINAL)
        ->completedAt('20250329143330'))
    ->result(fn ($r) => $r
        ->test('PSA')
        ->value('2.1')
        ->units('ng/mL')
        ->referenceRangeLimit('encode($message);

$decoder = new LlpDecoder(verifyChecksums: true);
$decoder->feed($chunk);
while ($raw = $decoder->popMessage()) {
    $parsed = (new \Astm\Parser())->parse($raw);
}

$checksum = Frame::checksum('1H|\\^&|||SENDER' . Ascii::ETX);
```

---

Utility Classes
---------------

[](#utility-classes)

### EscapeCodec

[](#escapecodec)

```
$codec = Astm::escapeCodec();
$safe  = $codec->encode('Result: 5.0 | note');   // "Result: 5.0 &F& note"
$plain = $codec->decode($safe);
```

SequenceLiteral`&F&``|` field delimiter`&R&``\` repeat delimiter`&S&``^` component delimiter`&E&``&` escape character### MessageValidator

[](#messagevalidator)

```
$errors = Astm::validate($message);  // [] = valid
```

### MessageCollection

[](#messagecollection)

```
$collection = Astm::parseFile('/batch/session.astm');

$collection->getAbnormalResults();              // all H/L/A across all sessions
$collection->getHighResults();                  // H/HH only
$collection->getLowResults();                   // L/LL only
$collection->getResultsByTest('WBC');           // cross-session
$collection->getMessagesWithAbnormalities();    // messages with any flag
$collection->getAllResultsMapped();             // grouped by test name
```

### MessageDiff

[](#messagediff)

```
$diff = Astm::diff($original, $corrected);

$diff->hasDifferences();         // bool
$diff->getFieldChanges();        // [{type, seq, field, old, new}, ...]
$diff->getChangedResults();      // [{test, old, new}, ...]
$diff->getChangedTestNames();    // ['WBC', 'HGB']
$diff->getSummary();             // ['R[1] field 4: "7.2" → "99.9"', ...]
```

### DateTimeHelper

[](#datetimehelper)

```
use Astm\DateTimeHelper;

DateTimeHelper::parse('20250329143330');     // DateTimeImmutable
DateTimeHelper::parseDate('19920801');       // DateTimeImmutable
DateTimeHelper::format($dt);                 // '20250329143330'
DateTimeHelper::formatDate($dt);             // '19920801'
DateTimeHelper::now();                       // current UTC as YYYYMMDDHHMMSS
DateTimeHelper::today();                     // current UTC date as YYYYMMDD
DateTimeHelper::isValid('20250329143330');   // true
```

---

Serialisation
-------------

[](#serialisation)

```
// Wire format
$message->toString("\r");     // ASTM CR-terminated (instrument wire format)
$message->toString("\n");     // newline-separated (logs / files)

// PHP array
$arr = $message->toArray();
// keys: sender, version, datetime, patient, order, results, comments

// JSON
$json = $message->toJson();                           // pretty-printed
$json = $message->toJson(JSON_THROW_ON_ERROR);        // compact

// LLP binary file
Astm::writeFile($message, '/tmp/output.astm');
$collection = Astm::readFile('/tmp/output.astm');
```

---

ASTM Record Reference
---------------------

[](#astm-record-reference)

### Result flags

[](#result-flags)

ConstantValueMeaning`Result::FLAG_NORMAL``N`Within range`Result::FLAG_ABOVE_NORMAL``H`High`Result::FLAG_BELOW_NORMAL``L`Low`Result::FLAG_CRITICAL_H``HH`Critically high`Result::FLAG_CRITICAL_L``LL`Critically low`Result::FLAG_ABNORMAL``A`Qualitative abnormal`Result::FLAG_WARNING``W`Instrument warning### Result statuses

[](#result-statuses)

ConstantValue`Result::STATUS_FINAL``F``Result::STATUS_CORRECTION``C``Result::STATUS_PRELIMINARY``P``Result::STATUS_INCOMPLETE``I`### Exception hierarchy

[](#exception-hierarchy)

```
AstmException
├── ParseException
│   └── UnknownRecordTypeException
└── ConnectionException

```

---

Examples
--------

[](#examples)

```
php examples/01_parse_file.php                    # parse + display with flags
php examples/02_build_and_send.php [host] [port]  # build, validate, JSON, send
php examples/03_tcp_server.php [host] [port]      # TCP server (Ctrl-C to stop)
php examples/04_batch_collection.php              # batch stats report
php examples/05_modify_and_forward.php            # clone + enrich + file round-trip
php examples/06_serial_instrument.php /dev/ttyUSB0 9600  # RS-232 (hardware)
php examples/07_escape_and_delimiters.php         # EscapeCodec + custom delimiters
```

---

Testing
-------

[](#testing)

```
composer install
composer test
```

**167 tests · 371 assertions** across 5 test files covering the full stack.

---

Extending
---------

[](#extending)

### Custom record type

[](#custom-record-type)

```
use Astm\Records\AbstractRecord;

final class ManufacturerRecord extends AbstractRecord
{
    public const TYPE = 'M';
    public function getType(): string { return self::TYPE; }
    public function getVendorData(): string { return $this->getField(2); }
}

\Astm\Parser::registerRecordType('M', ManufacturerRecord::class);
$message  = (new \Astm\Parser())->parse($raw);
$mRecords = $message->getRecordsByType('M');
```

### Custom transport

[](#custom-transport)

Implement `Astm\Transport\TransportInterface` and pass to `Sender` or `Receiver`.

---

Publishing to Packagist
-----------------------

[](#publishing-to-packagist)

See [PACKAGIST.md](PACKAGIST.md) for step-by-step instructions.

---

Security
--------

[](#security)

Please report vulnerabilities via the process in [SECURITY.md](SECURITY.md).

---

Changelog
---------

[](#changelog)

See [CHANGELOG.md](CHANGELOG.md).

---

Contributing
------------

[](#contributing)

See [CONTRIBUTING.md](CONTRIBUTING.md).

---

Licence
-------

[](#licence)

MIT — see [LICENSE](LICENSE).

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance87

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

63d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/78b0a7debf5228ca5c747723226ed05629bfefc74568a1a2f552c45f1a54faed?d=identicon)[josiahke](/maintainers/josiahke)

---

Top Contributors

[![josiahke](https://avatars.githubusercontent.com/u/8400694?v=4)](https://github.com/josiahke "josiahke (2 commits)")

---

Tags

analyserhl7instrumentLLPlaboratoryastme1394e1394-97e1381lislimsclinicalhaematology

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/orderlyconnect-astm-e1394-91/health.svg)

```
[![Health](https://phpackages.com/badges/orderlyconnect-astm-e1394-91/health.svg)](https://phpackages.com/packages/orderlyconnect-astm-e1394-91)
```

###  Alternatives

[aranyasen/hl7

HL7 parser, generator and sender.

1971.6M](/packages/aranyasen-hl7)[mck89/peast

Peast is PHP library that generates AST for JavaScript code

19037.7M41](/packages/mck89-peast)[sauladam/shipment-tracker

Parses tracking information for several carriers, like UPS, USPS, DHL and GLS by simply scraping the data. No need for any kind of API access.

9642.0k](/packages/sauladam-shipment-tracker)[json-mapper/laravel-package

The JsonMapper package for Laravel

25188.9k3](/packages/json-mapper-laravel-package)[jamesmoss/toml

A parser for TOML implemented in PHP.

3231.7k15](/packages/jamesmoss-toml)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
