Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable Gain control for Vitals Monitor #5749

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 59 additions & 8 deletions src/Components/Assets/AssetType/HL7Monitor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import CareIcon from "../../../CAREUI/icons/CareIcon";
import TextFormField from "../../Form/FormFields/TextFormField";
import HL7PatientVitalsMonitor from "../../VitalsMonitor/HL7PatientVitalsMonitor";
import VentilatorPatientVitalsMonitor from "../../VitalsMonitor/VentilatorPatientVitalsMonitor";
import CheckBoxFormField from "../../Form/FormFields/CheckBoxFormField";

interface HL7MonitorProps {
assetId: string;
Expand All @@ -31,6 +32,8 @@ const HL7Monitor = (props: HL7MonitorProps) => {
const [isLoading, setIsLoading] = useState(true);
const [localipAddress, setLocalIPAddress] = useState("");
const [ipadrdress_error, setIpAddress_error] = useState("");
const [gains, setGains] = useState<any>();
const [editGains, setEditGains] = useState(false);

const dispatch = useDispatch<any>();

Expand All @@ -50,20 +53,34 @@ const HL7Monitor = (props: HL7MonitorProps) => {
setAssetType(asset?.asset_class);
setMiddlewareHostname(asset?.meta?.middleware_hostname);
setLocalIPAddress(asset?.meta?.local_ip_address);
if (asset?.meta?.gains) {
setGains(asset?.meta?.gains);
setEditGains(true);
} else {
setGains(
asset?.asset_class === "HL7MONITOR"
? { ecg: 1, pleth: 1, resp: 1 }
: { pressure: 1, flow: 1, volume: 1 }
);
setEditGains(false);
}
setIsLoading(false);
}, [asset]);

const handleSubmit = async (e: React.SyntheticEvent) => {
e.preventDefault();
if (checkIfValidIP(localipAddress)) {
setIpAddress_error("");

const data = {
meta: {
asset_type: assetType,
middleware_hostname: middlewareHostname, // TODO: remove this infavour of facility.middleware_address
local_ip_address: localipAddress,
gains: editGains ? gains : undefined,
},
};

const res: any = await Promise.resolve(
dispatch(partialUpdateAsset(assetId, data))
);
Expand All @@ -87,31 +104,63 @@ const HL7Monitor = (props: HL7MonitorProps) => {
return (
<div className="flex w-full mx-auto xl:mt-8">
<div className="flex flex-col xl:flex-row-reverse gap-4 mx-auto">
<div className="w-full xl:max-w-xs shrink-0 flex flex-col gap-4">
<div className="w-full xl:max-w-lg shrink-0 flex flex-col gap-4">
<Card className="w-full flex flex-col">
<form onSubmit={handleSubmit}>
<h2 className="text-lg font-bold mb-2">Connection</h2>
<div className="flex flex-col">
<div className="flex items-center justify-between mb-2">
<h2 className="text-lg font-bold">Connection</h2>
<Submit ghost border size="small">
<CareIcon className="care-l-save text-lg" />
<span>Save</span>
</Submit>
</div>
<div className="flex flex-col md:flex-row gap-2">
<TextFormField
labelClassName="!text-sm"
name="middlewareHostname"
label="Middleware Hostname"
placeholder={facilityMiddlewareHostname}
value={middlewareHostname}
onChange={(e) => setMiddlewareHostname(e.value)}
errorClassName="hidden"
/>
<TextFormField
labelClassName="!text-sm"
name="localipAddress"
label="Local IP Address"
value={localipAddress}
onChange={(e) => setLocalIPAddress(e.value)}
required
error={ipadrdress_error}
/>
<Submit className="w-full">
<CareIcon className="care-l-save" />
<span>Save Configuration</span>
</Submit>
</div>
<div className="flex items-center justify-between mb-2">
<h2 className="text-lg font-bold">Waveform Configurations</h2>
<CheckBoxFormField
labelClassName="!text-sm"
name="editGains"
label="Override Gains"
value={editGains}
onChange={(e) => setEditGains(e.value)}
errorClassName="hidden"
/>
</div>
<div className="flex flex-col md:flex-row gap-2">
{gains &&
Object.keys(gains).map((key) => (
<TextFormField
disabled={!editGains}
labelClassName="!text-sm"
name={key}
label={`${key.toUpperCase()} Gain`}
placeholder="1.00"
value={(gains[key] ?? 1).toString()}
type="number"
onChange={(e) =>
setGains((prev: any) => ({ ...prev, [key]: e.value }))
}
step={0.01}
/>
))}
</div>
</form>
</Card>
Expand All @@ -124,7 +173,9 @@ const HL7Monitor = (props: HL7MonitorProps) => {

{assetType === "HL7MONITOR" && (
<HL7PatientVitalsMonitor
key={JSON.stringify(gains)}
socketUrl={`wss://${middleware}/observations/${localipAddress}`}
options={{ gains }}
/>
)}
{assetType === "VENTILATOR" && (
Expand Down
17 changes: 9 additions & 8 deletions src/Components/Assets/configure/MonitorConfigure.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
import * as Notification from "../../../Utils/Notifications.js";
import { useDispatch } from "react-redux";
import { Submit } from "../../Common/components/ButtonV2";
import { FieldLabel } from "../../Form/FormFields/FormField";

const saveLink = (assetId: string, bedId: string, dispatch: Dispatch<any>) => {
dispatch(createAssetBed({}, assetId, bedId));
Expand Down Expand Up @@ -75,9 +74,15 @@ export default function MonitorConfigure({ asset }: { asset: AssetData }) {
}
}}
>
<div className="flex flex-col">
<div className="w-full">
<FieldLabel className="">Bed</FieldLabel>
<div className="w-full">
<div className="flex justify-between items-center mb-2">
<h2 className="text-lg font-bold">Bed</h2>
<Submit ghost border size="small">
<i className="fas fa-bed-pulse" />
{updateLink ? "Update" : "Save"}
</Submit>
</div>
<div className="flex items-center gap-2">
<BedSelect
name="bed"
setSelected={(selected) => setBed(selected as BedModel)}
Expand All @@ -89,10 +94,6 @@ export default function MonitorConfigure({ asset }: { asset: AssetData }) {
className="w-full"
/>
</div>
<Submit className="shrink-0 w-full mt-6">
<i className="fas fa-bed-pulse" />
{updateLink ? "Update Bed" : "Save Bed"}
</Submit>
</div>
</form>
);
Expand Down
2 changes: 2 additions & 0 deletions src/Components/Form/FormFields/TextFormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type TextFormFieldProps = FormFieldBaseProps<string> & {
leadingPadding?: string | undefined;
min?: string | number;
max?: string | number;
step?: string | number;
onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;
onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
};
Expand Down Expand Up @@ -59,6 +60,7 @@ const TextFormField = React.forwardRef((props: TextFormFieldProps, ref) => {
onFocus={props.onFocus}
onBlur={props.onBlur}
onChange={(e) => field.handleChange(e.target.value)}
step={props.step}
/>
);

Expand Down
4 changes: 2 additions & 2 deletions src/Components/VitalsMonitor/HL7DeviceClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { VitalsDataBase, VitalsValueBase, VitalsWaveformBase } from "./types";
const WAVEFORM_KEY_MAP: Record<string, EventName> = {
II: "ecg-waveform",
Pleth: "pleth-waveform",
Respiration: "spo2-waveform",
Respiration: "resp-waveform",
};

/**
Expand Down Expand Up @@ -101,7 +101,7 @@ type EventName =
| HL7MonitorData["observation_id"]
| "ecg-waveform"
| "pleth-waveform"
| "spo2-waveform";
| "resp-waveform";

const parseObservations = (data: string) => {
return JSON.parse(data || "[]") as HL7MonitorData[];
Expand Down
11 changes: 7 additions & 4 deletions src/Components/VitalsMonitor/HL7PatientVitalsMonitor.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect } from "react";
import useHL7VitalsMonitor from "./useHL7VitalsMonitor";
import useHL7VitalsMonitor, { Options } from "./useHL7VitalsMonitor";
import { PatientAssetBed } from "../Assets/AssetTypes";
import { Link } from "raviger";
import { GENDER_TYPES } from "../../Common/constants";
Expand All @@ -11,14 +11,17 @@ interface Props {
patientAssetBed?: PatientAssetBed;
socketUrl: string;
size?: { width: number; height: number };
options?: Options;
}

export default function HL7PatientVitalsMonitor({
patientAssetBed,
socketUrl,
size,
options,
}: Props) {
const { connect, waveformCanvas, data, isOnline } = useHL7VitalsMonitor();
const { connect, waveformCanvas, data, isOnline } =
useHL7VitalsMonitor(options);
const { patient, bed } = patientAssetBed ?? {};

useEffect(() => {
Expand Down Expand Up @@ -145,10 +148,10 @@ export default function HL7PatientVitalsMonitor({
<div className="flex justify-between items-center p-1">
<div className="flex gap-2 items-start h-full text-yellow-300 font-bold">
<span className="text-sm">SpO2</span>
<span className="text-xs">{data.spo2?.unit ?? "--"}</span>
<span className="text-xs">{data.resp?.unit ?? "--"}</span>
</div>
<span className="text-4xl md:text-6xl font-black text-yellow-300 mr-3">
{data.spo2?.value ?? "--"}
{data.resp?.value ?? "--"}
</span>
</div>

Expand Down
22 changes: 11 additions & 11 deletions src/Components/VitalsMonitor/HL7VitalsRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ interface Options {
/**
* Options for SPO2 channel.
*/
spo2: ChannelOptions;
resp: ChannelOptions;
}

/**
Expand All @@ -67,7 +67,7 @@ class HL7VitalsRenderer {
const {
ecg,
pleth,
spo2,
resp,
size: { height: h, width: w },
} = options;

Expand Down Expand Up @@ -95,38 +95,38 @@ class HL7VitalsRenderer {
rows: 1,
},

spo2: {
resp: {
color: "#03a9f4",
buffer: [],
cursor: { x: 0, y: 0 },
deltaX: w / (DURATION * spo2.samplingRate),
transform: lerp(spo2.lowLimit, spo2.highLimit, h, h * 0.75),
chunkSize: spo2.samplingRate * options.animationInterval * 1e-3,
options: spo2,
deltaX: w / (DURATION * resp.samplingRate),
transform: lerp(resp.lowLimit, resp.highLimit, h, h * 0.75),
chunkSize: resp.samplingRate * options.animationInterval * 1e-3,
options: resp,
rows: 1,
},
};

// Draw baseline for each channel.
this.initialize(this.state.ecg);
this.initialize(this.state.pleth);
this.initialize(this.state.spo2);
this.initialize(this.state.resp);

// Start rendering.
setInterval(() => {
this.render(this.state.ecg);
this.render(this.state.pleth);
this.render(this.state.spo2);
this.render(this.state.resp);
}, options.animationInterval);
}

private options: Options;
private state: { ecg: ChannelState; pleth: ChannelState; spo2: ChannelState };
private state: { ecg: ChannelState; pleth: ChannelState; resp: ChannelState };

/**
* Appends data to the buffer of the specified channel.
*/
append(channel: "ecg" | "pleth" | "spo2", data: number[]) {
append(channel: "ecg" | "pleth" | "resp", data: number[]) {
const state = this.state[channel];
state.buffer.push(...data.map(state.transform));
}
Expand Down
Loading
Loading