Devices
Each device plugin must export a start
function that receives a context
object. This context contains everything you need to:
- Establish a connection to the device
- Receive or send data
- Produce FHIR resources (like
DiagnosticReport
,Observation
,Message
) - Communicate with other devices or systems
This guide walks through how to handle each connection type, emit results, and respond to requests from the LIS or other plugins.
π° Typing the Contextβ
Always type your context with the pluginβs automation definition.
import type { Context } from "@medxintl/automation";
import automation from "../path/to/automation.json";
export async function start(context: Context<typeof automation>) {
// your code here
}
π Supported Connection Methodsβ
These are the valid context.connection.method
values:
"serial"
β e.g. COM port"tcp"
β setup on a tcp server (e.g Sysmex XN Series): your plugin will create a server that handles incoming connections"tcp-as-server"
β connect to a tcp server (e.g. Mindray BC Series without a computer): here you are expected to connect a server that listens for incoming connections, once a connection is established you can use the socket to send and receive data"tcp-sender-and-receiver"
β dual channel (e.g. Abbott Architect): Here you are expected to create separate connections for sending and receiving data each with their own IP address and port."folder"
/"file"
β watch for incoming files"usb"
β treated like folder or serial
π Serial Connection + Result Emissionβ
import { openSerialPort } from "@medxintl/machines";
export async function start(context) {
const port = await openSerialPort(
context.connection.serialPort!,
context.connection.serialConfig
);
port.on("data", buffer => {
const raw = buffer.toString("utf-8");
context.produce("Message", {
resourceType: "Message",
content: raw,
status: "pending"
});
const parsed = parseResult(raw);
context.produce("DiagnosticReport", {
resourceType: "DiagnosticReport",
id: `report-${Date.now()}`,
status: "preliminary",
code: { text: "CBC" },
result: parsed.observations.map(o => ({ reference: `#${o.id}` })),
contained: parsed.observations
});
});
context.status("connected");
}
π TCP Client Connectionβ
import { connectTcpClient } from "@medxintl/machines";
export async function start(context) {
const socket = await connectTcpClient(
context.connection.tcpIpHost!,
context.connection.tcpIpPort!
);
socket.on("data", buffer => {
context.produce("Message", {
resourceType: "Message",
content: buffer.toString("utf-8"),
status: "pending"
});
});
context.status("connected");
}
π‘ TCP Server Connection (HL7 Listener)β
import { hl7 } from "@medxintl/machines";
export async function start(context) {
const app = hl7.app();
app.match("ORU_R01", (req, res, next) => {
const raw = req.raw;
const msg = req.msg;
context.produce("Message", {
resourceType: "Message",
content: raw,
status: "processed"
});
const parsed = parseHl7Result(msg); // your parser
context.produce("DiagnosticReport", convertToFhir(parsed));
res.ack.set("MSA.1", "AA");
res.end();
});
await app.start(context.connection.port!);
context.status("connected", `HL7 server on port ${context.connection.port}`);
}
π Folder Pollingβ
import { watchFolder } from "@medxintl/machines";
import fs from "fs";
export async function start(context) {
await watchFolder(context.connection.folderPath!, file => {
const raw = fs.readFileSync(file, "utf-8");
context.produce("Message", {
resourceType: "Message",
content: raw,
status: "pending"
});
});
context.status("connected");
}
π¬ Deferred Parsingβ
You can parse raw messages later:
export async function start(context) {
context.on("Message", message => {
const parsed = parseResult(message.content);
context.produce("DiagnosticReport", {
resourceType: "DiagnosticReport",
id: `report-${Date.now()}`,
status: "preliminary",
result: parsed.observations.map(o => ({ reference: `#${o.id}` })),
contained: parsed.observations
});
});
context.status("connected");
}
π₯ Receiving Service Requests + Sending HL7 Orderβ
import { hl7, buildOrmMessage } from "@medxintl/machines";
export async function start(context) {
const app = hl7.app();
await app.connect(
context.connection.tcpIpHost!,
context.connection.tcpIpPort!,
"utf-8"
);
context.status("connected", "Connected to HL7 device");
context.on("ServiceRequest", request => {
const patientId =
request.subject?.reference?.replace("Patient/", "") ?? "UNKNOWN";
const testCode = request.code?.coding?.[0]?.code ?? "CBC";
const hl7Message = buildOrmMessage({
patientId,
orderId: request.id ?? `ORD-${Date.now()}`,
testCode
});
app
.send(hl7Message)
.then(() => {
context.status("sent", `Order sent for test ${testCode}`);
})
.catch(err => {
context.status("error", `Failed to send HL7: ${err.message}`);
});
});
}
π§© Producing Messages First (Always Required)β
Your plugin must always produce a Message
before parsing.
Why?
- Ensures traceability
- Allows deferred parsing
- Enables other plugins to subscribe to raw input
context.produce("Message", {
resourceType: "Message",
content: raw,
status: "pending"
});
β Quick Referenceβ
Function | Purpose |
---|---|
context.connection | IP, port, COM, folder path, etc. |
context.status() | Updates plugin connection status |
context.produce() | Emits a resource (Observation , Message , etc.) |
context.on() | Subscribe to resource updates |