Skip to main content

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​

FunctionPurpose
context.connectionIP, 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