std.hl7 — HL7 v2 Messaging
Fünf Units für HL7 Version 2 — den im Gesundheitswesen meistverbreiteten Nachrichtenstandard. std.hl7 deckt den gesamten Stack ab: MLLP-TCP-Framing, MSH/ACK-Engine, Patientenverwaltung (ADT), Auftragswesen (ORM/OML/RAS), Befundübermittlung (ORU/OUL) und Terminplanung (SIU).
→ Übersicht · Standard Library · Welche Unit?
Alle Beispiele dieser Seite sind mit lyxc 1.0.21A übersetzt und ausgeführt; die gezeigten Ausgaben sind die echten Programmausgaben.
Units
| Unit | WP | Beschreibung |
|---|---|---|
| std.hl7.core | WP-HL7-00 | MLLP-Framing (VT+FS+CR), MSH-Parser, ACK-Generator, Duplikatserkennung (FNV1a-Ring), Versions- und Testmodus-Prüfung |
| std.hl7.adt | WP-HL7-01 | Patient Administration: ADT^Axx parsen/schreiben; PID, PV1, MRG, NK1; automatische Merge-Erkennung (A34–A45) |
| std.hl7.orders | WP-HL7-02 | Order Management: ORM^O01, OML^O21, RAS^O17 parsen/schreiben; ORC, OBR, RXA; STAT-Erkennung; ORR^O02-Quittung |
| std.hl7.results | WP-HL7-03 | Result Reporting: ORU^R01, OUL^R22 parsen/schreiben; OBX (Panikwert- und Korrektur-Flags), NTE-Kommentare |
| std.hl7.scheduling | WP-HL7-04 | Terminplanung: SIU^S12–S26 parsen/schreiben; SCH, AIS, AIG, AIL; Flags für Absage (S15), Löschung (S17), Nicht-Erscheinen (S26) |
Alle vier Fachunits importieren std.hl7.core — import std.hl7.adt; zieht den Kern also mit.
^in DokuWiki-Tabellen. Der Nachrichtentyp wird in HL7 mit^geschrieben (ADT^A01). In einer Wiki-Tabelle trennt genau dieses Zeichen die Zellen — deshalb steht es hier überall in…. Wer diese Seite bearbeitet: bitte beibehalten, sonst zerfällt die Tabelle in Spalten.
Was ist HL7 v2?
HL7 (Health Level 7) Version 2 ist der faktische Standard für Datenintegration in Krankenhäusern. Jede Nachricht besteht aus zeilenweise (CR) geschriebenen Segmenten (3 Großbuchstaben + Feldtrenner), die |-getrennte Felder und ^-getrennte Komponenten enthalten:
MSH|^~\&|LIS|HOSPITAL|HIS||20260616083000||ORU^R01|MSG001|P|2.5.1
PID|||PAT123^^^MPI||Muster^Max||19850315|M
OBR|1|ORD042|LAB042|1743-4^Glucose^LN|||20260616082000
OBX|1|NM|1743-4^Glucose^LN||195|mg/dL|70-99||||HH||F
Die Standard-Trennzeichen stehen in MSH-1 und MSH-2 und werden von Hl7MshParse aus der Nachricht gelesen, nicht angenommen:
| Ebene | Zeichen | Beispiel |
|---|---|---|
| Feld | | | MSH|^~\&|LIS |
| Komponente | ^ | Muster^Max |
| Wiederholung | ~ | ID1~ID2 |
| Escape | \ | \F\ für ein literales | |
| Subkomponente | & | A&B |
MLLP-Framing
HL7 v2-Nachrichten werden über TCP immer im MLLP-Frame übertragen (Minimal Lower Layer Protocol):
[VT=0x0B] + HL7-Nachrichtenbytes + [FS=0x1C] + [CR=0x0D]
MllpFrameWrite legt den Rahmen um eine fertige HL7-Nachricht, MllpFrameRead schält die Nachricht aus dem TCP-Empfangspuffer. MllpFrameRead sucht das VT auch mitten im Puffer — Datenmüll davor wird übersprungen.
Designprinzipien
- Zero-Copy-Parsing: Alle Parse-Funktionen speichern Zeiger und Längen in den Originalnachrichtenpuffer — keine String-Kopien. Der Puffer muss leben, solange die Struct benutzt wird.
- Caller alloziert: Alle Structs (MSH, PID, PV1, ADT, Order, Report, OBX, Appointment) legt der Aufrufer selbst an. Die Größe steht in den
HL7_*_SIZE-Konstanten. - Kein rekursiver Besitz: Eine geparste ORU-Nachricht liefert nur den Befundkopf (
Hl7OruParse) — die einzelnen OBX-Segmente werden getrennt mitHl7ObxParsein einer Schleife geparst. Genauso bei NTE, AIS, AIG und AIL. - Abgeleitete Flags:
HL7_ORD_ISSTAT,HL7_OBX_ISCRIT/HL7_OBX_ISCORRsowieHL7_APPT_ISCANCEL/ISDELETE/ISNOSHOWsetzt der Parser selbst — keine manuellen String-Vergleiche nötig.
Zeiger aus einer Struct sind nicht nullterminiert. Jedes Feld liegt als Paar vor: OffsetXhält den Zeiger, OffsetX_LENdie Länge. Wer den Wert anPrintLnoderStrConcatübergibt, druckt den Rest der Nachricht mit. Für die Ausgabe eine terminierte Kopie anlegen — siehe die FunktionFeld()im Quickstart unten.
Nachrichtenflüsse
Patientenaufnahme (ADT)
HIS/ADT-System HL7-Gateway / Lyx
| |
|-- ADT^A01 (Aufnahme) ->| Hl7AdtParse()
|<-- ACK^AA (Quittung) --| Hl7AckWrite() + MllpFrameWrite()
| |
|-- ADT^A08 (Update) --->| Hl7AdtParse()
|<-- ACK^AA -------------|
| |
|-- ADT^A03 (Entlassung)>| Hl7AdtParse()
|<-- ACK^AA -------------|
Laborauftrag und Befund (ORM → ORU)
KIS / Station LIS (Labor)
| |
|-- ORM^O01 (Auftrag) -->| Hl7OrmParse()
|<-- ORR^O02 (OK/ER) ----| Hl7OrrWrite()
| | [Analyse laeuft ...]
|<-- ORU^R01 (Befund) ---| Hl7OruParse() + Hl7ObxParse() je OBX
|-- ACK^AA ------------->|
Medikamentengabe (RAS)
Medikamentensystem Stationssystem
| |
|-- RAS^O17 ------------>| Hl7RasParse()
|<-- ACK^AA -------------|
Terminvergabe (SIU)
Terminplaner Fachabteilung
| |
|-- SIU^S12 (neu) ------>| Hl7SiuParse()
|-- SIU^S15 (Absage) --->| HL7_APPT_ISCANCEL = 1
|-- SIU^S26 (No-Show) -->| HL7_APPT_ISNOSHOW = 1
|<-- ACK^AA -------------|
Funktionsübersicht
std.hl7.core
| Funktion | Rückgabe | Zweck |
|---|---|---|
Hl7StateInit(state) | – | Dedup-State nullen; einmalig pro Instanz |
MllpFrameWrite(buf, bufMax, msg, msgLen) | Bytes oder -1 | VT+Nachricht+FS+CR schreiben |
MllpFrameRead(buf, bufLen, out, outMax) | Länge, -1 (kein Frame), -2 (out zu klein) | Nachricht aus dem Frame schälen |
Hl7MshParse(msg, len, msh) | HL7_OK / HL7_ERR_SYNTAX | MSH-Segment in die Struct parsen |
Hl7AckWrite(msh, code, text, out, outMax) | Bytes oder -1 | ACK/NACK erzeugen; code ist "AA"c, "AE"c oder "AR"c als pchar |
Hl7DupCheck(state, partnerKey, ctrlId) | 1 = schon gesehen, 0 = neu | Duplikate je Partner über FNV1a-Ring (64 Einträge) |
Hl7VersionCheck(msh) | HL7_OK / HL7_ERR_VERSION | Unterstützt 2.3.1, 2.4, 2.5, 2.5.1, 2.6, 2.7 |
Hl7IsTestMsg(msh) | 1 = Testnachricht | MSH-11 beginnt mit T — nicht in die Produktivdatenbank schreiben |
Hl7IsUtf8(msh) | 1 = UTF-8 | MSH-18 = UNICODE…; ohne MSH-18 gilt ISO-8859-1, nicht UTF-8 |
Hl7DupCheck registriert die Nachricht beim Rückgabewert 0 gleich mit — ein zweiter Aufruf mit derselben Control-ID liefert 1.
std.hl7.adt
| Funktion | Zweck |
|---|---|
Hl7AdtParse(msg, len, adt) | Ganze Nachricht: MSH-Kopf, PID, PV1, MRG, NK1 in eine Hl7Adt-Struct |
Hl7AdtWrite(adt, event, out, outMax) | Nachricht aus der Struct schreiben; event z. B. "A01"c |
Hl7PidParse · Hl7Pv1Parse · Hl7MrgParse · Hl7Nk1Parse | Einzelsegment parsen (Zeiger auf das Segment, nicht auf die Nachricht) |
Hl7AdtIsMerge(eventPtr, eventLen) | 1 bei A34–A45 (Merge-/Move-Ereignisse) |
std.hl7.orders
| Funktion | Zweck |
|---|---|
Hl7OrmParse · Hl7OmlParse | Auftragsnachricht in eine Hl7Order-Struct |
Hl7RasParse(msg, len, admin) | Medikamentengabe in eine Hl7RxAdmin-Struct |
Hl7OrcParse · Hl7ObrParse · Hl7RxaParse | Einzelsegmente |
Hl7OrmWrite · Hl7OmlWrite | Auftrag schreiben |
Hl7OrrWrite(order, status, out, outMax) | Auftragsquittung ORR^O02 |
HL7_ORD_MSGTYPE unterscheidet die Herkunft (1 = ORM, 2 = OML), HL7_ORD_ISSTAT meldet einen STAT-Auftrag (OBR-5 = S).
std.hl7.results
| Funktion | Zweck |
|---|---|
Hl7OruParse · Hl7OulParse | Befundkopf (OBR + PID) in eine Hl7Report-Struct |
Hl7ObxParse(seg, segLen, obx) | Ein OBX-Segment; je Beobachtung einmal aufrufen |
Hl7NteParse(seg, segLen, note) | Kommentarsegment |
Hl7OruWrite(report, obx, out, outMax) | Befund schreiben |
HL7_RPT_MSGTYPE ist HL7_RPT_TYPE_ORU (1) oder HL7_RPT_TYPE_OUL (2). HL7_OBX_ISCRIT markiert Panikwerte (OBX-8 HH/LL), HL7_OBX_ISCORR korrigierte Ergebnisse (OBX-11 C).
std.hl7.scheduling
| Funktion | Zweck |
|---|---|
Hl7SiuParse(msg, len, appt) | Terminnachricht in eine Hl7Appointment-Struct |
Hl7SiuWrite(appt, out, outMax) | Termin schreiben |
Hl7SchParse · Hl7AisParse · Hl7AigParse · Hl7AilParse | Einzelsegmente: Termin, Leistung, Ressource, Ort |
Rückgabewerte
| Konstante | Wert | Bedeutung |
|---|---|---|
HL7_OK | 0 | erfolgreich |
HL7_ERR_TRUNC | 1 | Nachricht abgeschnitten |
HL7_ERR_SYNTAX | 2 | Segment nicht lesbar (z. B. kein MSH am Anfang) |
HL7_ERR_DUP | 3 | Duplikat |
HL7_ERR_OVERFLOW | 4 | Zielpuffer zu klein |
HL7_ERR_VERSION | 5 | HL7-Version nicht unterstützt |
Die Schreib-Funktionen (…Write) melden Überlauf dagegen mit -1 als Rückgabewert, nicht mit HL7_ERR_OVERFLOW — der Rückgabewert ist dort die geschriebene Bytezahl.
HL7_ACK_AA / HL7_ACK_AE / HL7_ACK_AR (1/2/3) sind Kennzahlen für eigene Zustandslogik. Hl7AckWrite erwartet nicht sie, sondern den zweibuchstabigen Code als pchar — seit #1617 ohne Cast: "AA"c.
Struct-Größentabelle
| Struct | Konstante | Bytes | Unit | Zweck |
|---|---|---|---|---|
| Hl7Msh | HL7_MSH_SIZE | 192 | core | MSH-Header einer beliebigen Nachricht |
| Hl7Slot | HL7_SLOT_SIZE | 528 | core | Ein Partner-Slot des Dedup-Rings (16 + 64×8) |
| Hl7State | HL7_STATE_SIZE | 8448 | core | Dedup-State (16 Partner × 528 Bytes) |
| Hl7Pid | HL7_PID_SIZE | 176 | adt | Patient Identification |
| Hl7Pv1 | HL7_PV1_SIZE | 160 | adt | Patient Visit / Stationsbelegung |
| Hl7Mrg | HL7_MRG_SIZE | 48 | adt | Patient Merge |
| Hl7Nk1 | HL7_NK1_SIZE | 48 | adt | Next of Kin / Angehörige |
| Hl7Adt | HL7_ADT_SIZE | 544 | adt | Vollständige ADT-Nachricht (Kopf + PID + PV1 + MRG + NK1) |
| Hl7Order | HL7_ORD_SIZE | 192 | orders | Auftragsstruktur (ORC + OBR + PID + PV1) |
| Hl7RxAdmin | HL7_RXA_SIZE | 112 | orders | Medikamentengabe (RXA) |
| Hl7ObxResult | HL7_OBX_SIZE | 144 | results | Einzelbeobachtung mit Panikwert-Flag |
| Hl7NteNote | HL7_NTE_SIZE | 16 | results | Kommentar (NTE) |
| Hl7Report | HL7_RPT_SIZE | 104 | results | Befundkopf (OBR + PID, ohne OBX) |
| Hl7Sch | HL7_SCH_SIZE | 128 | scheduling | Terminsegment (SCH) |
| Hl7Ais | HL7_AIS_SIZE | 48 | scheduling | Terminleistung (AIS) |
| Hl7Aig | HL7_AIG_SIZE | 48 | scheduling | Terminressource (AIG) |
| Hl7Ail | HL7_AIL_SIZE | 32 | scheduling | Terminort (AIL) |
| Hl7Appointment | HL7_APPT_SIZE | 152 | scheduling | Vollständige SIU-Nachricht |
Die eingebetteten Structs der ADT-Nachricht liegen an festen Offsets: HL7_ADT_PID = 112, HL7_ADT_PV1 = 288, HL7_ADT_MRG = 448, HL7_ADT_NK1 = 496. Ein Feld daraus wird also mit peek64(adt + HL7_ADT_PID + HL7_PID_FAMILY) gelesen.
Quickstart
Empfangen, prüfen, parsen, quittieren — der vollständige Weg einer ADT-Nachricht:
import std.hl7.core;
import std.hl7.adt;
import std.alloc;
import std.string;
// Zero-Copy: die Struct-Felder zeigen in den Nachrichtenpuffer und sind
// NICHT nullterminiert. Fuer die Ausgabe eine Kopie mit \0 anlegen.
fn Feld(ptr: int64, len: int64): pchar {
var buf: int64 := alloc(len + 1);
var i: int64 := 0;
while (i < len) { poke8(buf + i, peek8(ptr + i)); i := i + 1; }
poke8(buf + len, 0);
return buf as pchar;
}
fn main(): int64 {
// Dedup-State: einmalig pro Server-Instanz
var state: int64 := alloc(HL7_STATE_SIZE);
Hl7StateInit(state);
// Eine Nachricht, wie sie aus dem Socket kommt (hier selbst gerahmt)
var msg: pchar := "MSH|^~\\&|HIS|KH|LIS|LAB|20260813081500||ADT^A01|MSG001|P|2.5.1\rPID|||PAT123^^^MPI||Muster^Max||19850315|M\rPV1||I|ITS^201^A^KH|||||||||||||||V4711\r"c;
var tcpBuf: int64 := alloc(65536);
var tcpLen: int64 := MllpFrameWrite(tcpBuf, 65536, msg as int64, StrLen(msg));
// MLLP-Frame auspacken
var msgBuf: int64 := alloc(65536);
var msgLen: int64 := MllpFrameRead(tcpBuf, tcpLen, msgBuf, 65536);
if (msgLen < 0) { return 1; }
// MSH parsen und pruefen
var msh: int64 := alloc(HL7_MSH_SIZE);
if (Hl7MshParse(msgBuf, msgLen, msh) != HL7_OK) { return 1; }
if (Hl7VersionCheck(msh) != HL7_OK) { return 1; }
if (Hl7IsTestMsg(msh) == 1) { PrintLn("Testnachricht - nicht speichern"); }
// Doppelte Nachricht? (Partnerschluessel: sendende Anwendung)
var sendApp: int64 := peek64(msh + HL7_MSH_SEND_APP);
var ctrlId: int64 := peek64(msh + HL7_MSH_CTRL_ID);
if (Hl7DupCheck(state, sendApp, ctrlId) == 1) { PrintLn("Duplikat"); return 0; }
// ADT vollstaendig parsen
var adt: int64 := alloc(HL7_ADT_SIZE);
Hl7AdtParse(msgBuf, msgLen, adt);
var famPtr: int64 := peek64(adt + HL7_ADT_PID + HL7_PID_FAMILY);
var famLen: int64 := peek64(adt + HL7_ADT_PID + HL7_PID_FAMILY_LEN);
var bedPtr: int64 := peek64(adt + HL7_ADT_PV1 + HL7_PV1_LBED);
var bedLen: int64 := peek64(adt + HL7_ADT_PV1 + HL7_PV1_LBED_LEN);
PrintLn(StrConcat("Nachname: ", Feld(famPtr, famLen)));
PrintLn(StrConcat("Bett: ", Feld(bedPtr, bedLen)));
// ACK bauen und rahmen
var ackBuf: int64 := alloc(512);
var ackLen: int64 := Hl7AckWrite(msh, "AA"c, "OK"c, ackBuf, 512);
var outBuf: int64 := alloc(600);
var outLen: int64 := MllpFrameWrite(outBuf, 600, ackBuf, ackLen);
PrintLn(StrConcat("ACK-Bytes: ", IntToStr(outLen)));
free(outBuf, 600); free(ackBuf, 512); free(adt, HL7_ADT_SIZE);
free(msh, HL7_MSH_SIZE); free(msgBuf, 65536); free(tcpBuf, 65536);
free(state, HL7_STATE_SIZE);
return 0;
}
Nachname: Muster
Bett: A
ACK-Bytes: 66
Das Bett kommt aus PV1-3.3: die Ortsangabe ITS^201^A^KH zerlegt der Parser in Station (ITS), Zimmer (201), Bett (A) und Einrichtung (KH).
Fallstricke
- Segmente trennt
\r, nicht\n. Eine mit Zeilenumbrüchen aufgebaute Nachricht wird nicht geparst; der Parser sucht CR (0x0D). - Zeiger überleben den Puffer nicht. Wird
msgBuffreigegeben oder wiederbenutzt, zeigen alle Felder der Structs ins Leere. Werte, die länger gebraucht werden, vorher kopieren. - Zeichensatz prüfen. Ohne MSH-18 gilt ISO-8859-1. Umlaute aus einem solchen Strom sind kein UTF-8 —
Hl7IsUtf8fragen, bevor der Text weitergereicht wird. - Testnachrichten erkennen.
Hl7IsTestMsgvor jedem Schreibzugriff auf die Produktivdatenbank aufrufen; MSH-11Tkommt in Kopplungstests regelmäßig vor. - Dedup-Ring ist begrenzt. 16 Partner × 64 Control-IDs. Bei mehr Partnern verdrängt der Ring die ältesten Einträge — die Duplikatserkennung ist eine Nahbereichs-Sicherung, kein Ersatz für eine persistente Nachrichtentabelle.
Hl7AckWriteerwartet den Code als Text ("AA"c), nicht die KonstanteHL7_ACK_AA.
Weiterführend: std.hl7.core · std.hl7.adt · std.hl7.orders · std.hl7.results · std.hl7.scheduling · std.net · Rohspeicher
Letzte Aktualisierung: 2026-08-13 — Tabellen repariert (^ in Zellen maskiert), „Vier Units„ → fünf, Funktionsübersicht je Unit, Rückgabewerte, Größentabelle um die scheduling-Structs und HL7_SLOT_SIZE ergänzt, Quickstart lauffähig gemacht (er benutzte eine nicht deklarierte tcpBufLen). Alles gegen std/hl7/*.lyx und lyxc 1.0.21A geprüft.
Codebeispiele geprüft: gegen lyxc 1.2.5C übersetzt (Prüflauf 2026-09-08 über die gesamte Doku: 574 Vollprogramme, 0 echte Fehler; zusätzlich 5159 Aufrufe gegen die pub fn-Signaturen in aurum/std gehalten, 0 Abweichungen).
