
A fully-open
Supply-chain resilient
pressure-control ventilator
for the people
The global COVID-19 pandemic has highlighted the need for a low-cost, rapidly-deployable ventilator, for the current as well as future respiratory virus outbreaks. While safe and robust ventilation technology exists in the commercial sector, the small number of capable suppliers cannot meet the severe demands for ventilators during a pandemic. Moreover, the specialized, proprietary equipment developed by medical device manufacturers is expensive and inaccessible in low-resource areas.
The People’s Ventilator Project (PVP) is an open-source, low-cost pressure-control ventilator designed for minimal reliance on specialized medical parts to better adapt to supply chain shortages. The PVP largely follows established design conventions, most importantly active and computer-controlled inhalation, together with passive exhalation. It supports pressure-controlled ventilation, combined with standard-features like autonomous breath detection, and the suite of FDA required alarms.
Read on here. <overview>
System Overview¶
The People’s Ventilator Project (PVP) is an open-source, low-cost pressure-control ventilator designed for minimal reliance on specialized medical parts to better adapt to supply chain shortages.
Hardware¶
The device components were selected to enable a minimalistic and relatively low-cost ventilator design, to avoid supply chain limitations, and to facilitate rapid and easy assembly. Most parts in the PVP are not medical-specific devices, and those that are specialized components are readily available and standardized across ventilator platforms, such as standard respiratory circuits and HEPA filters. We provide complete assembly of the PVP, including 3D-printable components, as well as justifications for selecting all actuators and sensors, as guidance to those who cannot source an exact match to components used in the Bill of Materials.
PVP Hardware¶
Components
Justifcation behind the components actuators and sensors selected for the PVP.
Assembly
Solidworks model of the system assembly, description of enclosure, and models for 3D printed components.
Electronics
Modular PCBs that interface the PVP actuators and sensors with the Raspberry Pi.
Bill of Materials
Itemized PVP parts list.
Software¶

PVP’s software was developed to bring the philosophy of free and open-source software to medical devices. PVP is not only open from top to bottom, but we have developed it as a framework for an adaptable, general-purpose, communally-developed ventilator.
PVP’s ventilation control system is fast, robust, and written entirely in high-level Python (3.7) – without the development and inspection bottlenecks of split computer/microprocessor systems that require users to read and write low-level hardware firmware.
All of PVP’s components are modularly designed, allowing them to be reconfigured and expanded for new ventilation modes and hardware configurations.
We provide complete API-level documentation and an automated testing suite to give everyone the freedom to inspect, understand, and expand PVP’s software framework.
PVP Modules¶
GUI
A modular GUI with intuitive controls and a clear alarm system that can be configured to control any parameter or display values from any sensor.
Controller
... Manuel write this
IO
A hardware abstraction layer powered by pigpio that can read/write at [x Hz]
Alarm
Define complex and responsive alarm triggering criteria with human-readable Alarm Rules
Common
Modules that provide the API between the GUI and controller, user preferences, and other utilities
Performance¶

Representative pressure control breath cycle waveforms for airway pressure and flow out. Test settings: compliance C=20 mL/cm H2O, airway resistance R=20 cm H2O/L/s, PIP=30 cm H2O, PEEP=5 cm H2O.¶
The completed system was tested with a standard test lung (QuickLung, IngMar Medical, Pittsburgh, PA) that allowed testing combinations of three lung compliance settings (C=5, 20, and 50 mL cm H2O) and three airway resistance settings (R=5, 20, and 50 cm H2O/L/s). The figure above shows pressure control performance for midpoint settings: C=20 mL/cm H2O, R=20 cm H2O/L/s, PIP=30 cm H2O, PEEP=5 cm H2O. PIP is reached within a 300 ms ramp period, then holds for the PIP plateau with minimal fluctuation of airway pressure for the remainder of the inspiratory cycle (blue). One the expiratory valve opens, exhalation begins and expiratory flow is measured (orange) as the airway pressure drops to PEEP and remains
there for the rest of the PEEP period.
Some manual adjustment of the pressure waveforms may be warranted depending on the patient, and such adjustment is permitted through a user flow adjustment setting. This flow adjustment setting allows the user to increase the maximum flow rate during the ramp cycle to inflate lungs with higher compliance. The flow setting can be readily changed from the GUI and the control system immediately adapts to the user’s input. An example of this flow adjustment is shown in the figure above for four breath cycles. While all cycles reach PIP, the latter two have a higher mean airway pressure, which may be more desirable under certain conditions than the lower mean airway pressure of the former two.
ISO Standards Testing¶
In order to characterize the PVP’s control over a wide range of conditions, we followed FDA Emergency Use Authorization guidelines, which specify ISO 80601-2-80-2018 for a battery of pressure controlled ventilator standard tests. We tested the conditions that do not stipulate a leak, and present the results here. For each configuration the following parameters are listed: the test number (from the table below), the compliance (C, mL/cm H2O), linear resistance (R, cm H2O/L/s), respiratory frequency (f, breaths/min), peak inspiratory pressure (PIP, cm H2O), positive end-expiratory pressure (PEEP, cm H2O), and flow adjustment setting.
Test number |
Intended delivered volume (mL) |
Compliance (mL (hPa)^-1) |
Linear resistance (hPa(L/s)^-1) +/- 10% |
Leakage (mL/min) +/- 10% |
Ventilatory frequency (breaths/min) |
Inspiratory time (s) |
Pressure (hPa) |
PEEP (hPa) |
---|---|---|---|---|---|---|---|---|
1 |
500 |
50 |
5 |
0 |
20 |
1 |
10 |
5 |
2 |
500 |
50 |
20 |
0 |
12 |
1 |
15 |
10 |
3 |
500 |
20 |
5 |
0 |
20 |
1 |
25 |
5 |
4 |
500 |
20 |
20 |
0 |
20 |
1 |
25 |
10 |
5 |
500 |
50 |
5 |
5000 |
20 |
1 |
25 |
5 |
6 |
500 |
50 |
20 |
10000 |
12 |
1 |
25 |
10 |
7 |
300 |
20 |
20 |
0 |
20 |
1 |
15 |
5 |
8 |
300 |
20 |
50 |
0 |
12 |
1 |
25 |
10 |
9 |
300 |
10 |
50 |
0 |
20 |
1 |
30 |
5 |
10 |
300 |
20 |
20 |
3000 |
20 |
1 |
25 |
5 |
11 |
300 |
20 |
50 |
6000 |
12 |
1 |
25 |
10 |
12 |
200 |
10 |
20 |
0 |
20 |
1 |
25 |
10 |
These tests cover an array of conditions, and more difficult test cases involve a high airway pressure coupled with a low lung compliance (case nos. 8 and 9). Under these conditions, if the inspiratory flow rate during the ramp phase is too high, the high airway resistance will produce a transient spike in airway pressure which can greatly overshoot the PIP value. For this reason, the system uses a low initial flow setting and allows the clinican to increase the flow rate if necessary.
The PVP integrates expiratory flow to monitor the tidal volume, which is not directly set in pressure controlled ventilation, but is an important parameter. Of the test conditions in the ISO standard, four that we tested intended a nominal delivered tidal volume of 500 mL, three intended 300 mL, and one intended 200 mL. For most cases, the estimated tidal volume has a tight spread clustered within 20% of the intended value.
Breath Detection¶
A patient-initiated breath after exhalation will result in a momentary drop in PEEP. PVP may optionally detect these transient decreases to trigger a new pressure-controlled breath cycle. We tested this functionality by triggering numerous breaths out of phase with the intended inspiratory cycle, using a QuickTrigger (IngMar Medical, Pittsburgh, PA) to momentarily open the test lung during PEEP and simulate this transient drop of pressure.
High Pressure Detection¶
Above is a demonstration of the PVP’s high airway pressure alarm (HAPA). An airway blockage results in a high airway pressure (above 60 cm H2O) that the system corrects within ~500 ms. Test settings: compliance C=20 mL/cm H2O, airway resistance R=20 cm H2O/L/s, PIP=30 cm H2O, PEEP=5 cm H2O.
Hardware Overview¶
The PVP components were selected to enable a minimalistic and relatively low-cost ventilator design, to avoid supply chain limitations, and to facilitate rapid and easy assembly. Most parts in the PVP are not medical-specific devices, and those that are specialized components are readily available and standardized across ventilator platforms, such as standard respiratory circuits and HEPA filters. We provide complete assembly of the PVP, including 3D-printable components, as well as justifications for selecting all actuators and sensors, as guidance to those who cannot source an exact match to components used in the Bill of Materials.
Actuators¶
Proportional solenoid valve
Expiratory valve
Sensors¶
Oxygen sensor
Proximal pressure sensor
Expiratory flow sensor
Components
Justifcation behind the components actuators and sensors selected for the PVP.
Assembly
Solidworks model of the system assembly, description of enclosure, and models for 3D printed components.
Electronics
Modular PCBs that interface the PVP actuators and sensors with the Raspberry Pi.
Bill of Materials
Itemized PVP parts list.
Components¶
The device components were selected to enable a minimalistic and relatively low-cost ventilator design, to avoid supply chain limitations, and to facilitate rapid and easy assembly. Most parts in our system are not medical-specific devices, and those that are specialized components are readily available and standardized across ventilator platforms, such as standard respiratory circuits and HEPA filters. Below, we provide justifications for selecting all actuators and sensors as guidance to those who cannot source an exact match to components used in the PVP.
Hardware Design¶
The following is a guided walk through the main hardware components that comprise the respiratory circuit, roughly following the flow of gas from the system inlet, to the patient, then out through the expiratory valve.
Hospital gas bleder. At the inlet to the system, we assume the presence of a commercial-off-the-shelf (COTS) gas blender. These devices mix air from U.S. standard medical air and O2 as supplied at the hospital wall at a pressure of around 50 psig. The device outlet fitting may vary, but we assume a male O2 DISS fitting (NIST standard). In field hospitals, compressed air and O2 cylinders may be utilized in conjunction with a gas blender, or a low-cost Venturi-based gas blender. We additionally assume that the oxygen concentration of gas supplied by the blender can be manually adjusted. Users will be able to monitor the oxygen concentration level in real-time on the device GUI.
Fittings and 3D printed adapters. Standardized fittings were selected whenever possible to ease part sourcing in the event that engineers replicating the system need to swap out a component, possibly as the result of sourcing constraints within their local geographic area. Many fittings are American national pipe thread (NPT) standard, or conform to the respiratory circuit tubing standards (15mm I.D./22 mm O.D.). To reduce system complexity and sourcing requirements of specialized adapters, a number of connectors, brackets, and manifold are provided as 3D printable parts. All 3D printed components were print-tested on multiple 3D printers, including consumer-level devices produced by MakerBot, FlashForge, and Creality3D.
Pressure regulator. The fixed pressure regulator near the inlet of the system functions to step down the pressure supplied to the proportional valve to a safe and consistent set level of 50 psi. It is essential to preventing the over-pressurization of the system in the event of a pressure spike, eases the real-time control task, and ensures that downstream valves are operating within the acceptable range of flow conditions.
Proportional valve. The proportional valve is the first of two actuated components in the system. It enables regulation of the gas flow to the patient via the PID control framework, described in a following section. A proportional valve upstream of the respiratory circuit enables the controller to modify the inspiratory time, and does not present wear limitations like pinch-valves and other analogous flow-control devices. The normally closed configuration was selected to prevent over-pressurization of the lungs in the event of system failure.
Sensors. The system includes an oxygen sensor for monitoring oxygen concentration of the blended gas supplied to the patient, a pressure sensor located proximally to the patient mouth along the respiratory circuit, and a spirometer, consisting of a plastic housing (D-Lite, GE Healthcare) with an attached differential pressure sensor, to measure flow. Individual sensor selection will be described in more detail in a following section. The oxygen sensor read-out is used to adjust the manual gas blender and to trigger alarm states in the event of deviations from a setpoint. The proximal location of the primary pressure sensor was selected due to the choice of a pressure-based control strategy, specifically to ensure the most accurate pressure readings with respect to the patient’s lungs. Flow estimates from the single expiratory flow sensor are not directly used in the pressure-based control scheme, but enable the device to trigger appropriate alarm states in order to avoid deviations from the tidal volume of gas leaving the lungs during expiration. The device does not currently monitor gas temperature and humidity due to the use of an HME rather than a heated humidification system.
Pressure relief. A critical safety component is the pressure relief valve (alternatively called the “pressure release valve”, or “pressure safety valve”). The proportional valve is controlled to ensure that the pressure of the gas supplied to the patient never rises above a set maximum level. The relief valve acts as a backup safety mechanism and opens if the pressure exceeds a safe level, thereby dumping excess gas to atmosphere. Thus, the relief valve in this system is located between the proportional valve and the patient respiratory circuit. The pressure relief valve we source cracks at 1 psi (approx 70 cm H2O).
Standard repisratory circuit. The breathing circuit which connects the patient to the device is a standard respiratory circuit: the flexible, corrugated plastic tubing used in commercial ICU ventilators. Because this system assumes the use of an HME/F to maintain humidity levels of gas supplied to the patient, specialized heated tubing is not required.
Anti-suffocation check valve. A standard ventilator check valve (alternatively called a “one-way valve”) is used as a secondary safety component in-line between the proportional valve and the patient respiratory circuit. The check valve is oriented such that air can be pulled into the system in the event of system failure, but that air cannot flow outward through the valve. A standard respiratory circuit check valve is used because it is a low-cost, readily sourced device with low cracking pressure and sufficiently high valve flow coefficient (Cv).
Bacterial filters. A medical-grade electrostatic filter is placed on either end of the respiratory circuit. These function as protection against contamination of device internals and surroundings by pathogens and reduces the probability of the patient developing a hospital-acquired infection. The electrostatic filter presents low resistance to flow in the airway.
HME. A Heat and Moisture Exchanger (HME) is placed proximal to the patient. This is used to passively humidify and warm air inspired by the patient. HMEs are the standard solution in the absence of a heated humidifier. While we evaluated the use of an HME/F which integrates a bacteriological/viral filter, use of an HME/F increased flow resistance and compromised pressure control.
Pressure sampling filter. Proximal airway pressure is sampled at a pressure port near the wye adapter, and measured by a pressure sensor on the sensor PCB. To protect the sensor and internals of the ventilator, an additional 0.2 micron bacterial/viral filter is placed in-line between the proximal airway sampling port and the pressure sensor. This is also a standard approach in many commercial ventilators.
Expiratory solenoid. The expiratory solenoid is the second of two actuated components in the system. When this valve is open, air bypasses the lungs, thereby enabling the lungs to de-pressurize upon expiration. When the valve is closed, the lungs may inflate or hold a fixed pressure, according to the control applied to the proportional valve. The expiratory flow control components must be selected to have a sufficiently high valve flow coefficient (Cv) to prevent obstruction upon expiration. This valve is also selected to be normally open, to enable the patient to expire in the event of system failure.
Manual PEEP valve. The PEEP valve is a component which maintains the positive end-expiratory pressure (PEEP) of the system above atmospheric pressure to promote gas exchange to the lungs. A typical COTS PEEP valve is a spring-based relief valve which exhausts when pressure within the airway exceeds a fixed limit. This limit is manually adjusted via compression of the spring. Various low-cost alternatives to a COTS mechanical PEEP valve exist, including the use of a simple water column, in the event that PEEP valves become challenging to source. We additionally provide a 3D printable PEEP valve alternative which utilizes a thin membrane, rather than a spring, to maintain PEEP.
Actuator Selection¶
When planning actuator selection, it was necessary to consider the placement of the valves within the larger system. Initially, we anticipated sourcing a proportional valve to operate at very low pressures (0-50 cm H20) and sufficiently high flow (over 120 LPM) of gas within the airway. However, a low-pressure, high-flow regime proportional valve is far more expensive than a proportional valve which operates within high-pressure (textasciitilde50 psi), high-flow regimes. Thus, we designed the device such that the proportional valve would admit gas within the high-pressure regime and regulate air flow to the patient from the inspiratory airway limb. Conceivably, it is possible to control the air flow to the patient with the proportional valve alone. However, we couple this actuator with a solenoid and PEEP valve to ensure robust control during PIP (peak inspiratory pressure) and PEEP hold, and to minimize the loss of O2-blended gas to the atmosphere, particularly during PIP hold.
Proportional valve sourcing. Despite designing the system such that the proportional valve could be sourced for operation within a normal inlet pressure regime (approximately 50 psi), it was necessary to search for a valve with a high enough valve flow coefficient (Cv) to admit sufficient gas to the patient. We sourced an SMC PVQ31-5G-23-01N valve with stainless steel body in the normally-closed configuration. This valve has a port size of 1/8” (Rc) and has previously been used for respiratory applications. Although the manufacturer does not supply Cv estimates, we empirically determined that this valve is able to flow sufficiently for the application.
Expiratory valve sourcing. When sourcing the expiratory solenoid, it was necessary to choose a device with a sufficiently high valve flow coefficient (Cv) which could still actuate quickly enough to enable robust control of the gas flow. A reduced Cv in this portion of the circuit would restrict the ability of the patient to exhale. Initially, a number of control valves were sourced for their rapid switching speeds and empirically tested, as Cv estimates are often not provided by valve manufacturers. Ultimately, however, we selected a process valve in lieu of a control valve to ensure the device would flow sufficiently well, and the choice of valve did not present problems when implementing the control strategy. The SMC VXZ250HGB solenoid valve in the normally-open configuration was selected. The valve in particular was sourced partially due to its large port size (3/4” NPT). If an analogous solenoid with rapid switching speed and large Cv cannot be sourced, engineers replicating our device may consider the use of pneumatically actuated valves driven from air routed from a take-off downstream of the pressure regulator.
Manual PEEP valve sourcing. The PEEP valve is one of the few medical-specific COTS components in the device. The system configuration assumes the use of any ventilator-specific PEEP valve (Teleflex, CareFusion, etc.) coupled with an adapter to the standard 22 mm respiratory circuit tubing. In anticipation of potential supply chain limitations, as noted previously, we additionally provide the CAD models of a 3D printable PEEP valve.
Sensor Selection¶
We selected a minimal set of sensors with analog outputs to keep the system design sufficiently adaptable. If there were a part shortage for a specific pressure sensor, for example, any readily available pressure sensor with an analog output could be substituted into the system following a simple adjustment in calibration in the controller. Our system uses three sensors: an oxygen sensor, an airway pressure sensor, and a flow sensor with availability for a fourth addition, all interfaced with the Raspberry Pi via a 4-channel ADC (Adafruit ADS1115) through an I2C connection.
Oxygen sensor. We selected an electrochemical oxygen sensor (Sensironics SS-12A) designed for the range of FiO2 used for standard ventilation and in other medical devices. The cell is self-powered, generating a small DC voltage (13-16 mV) that is linearly proportional to oxygen concentration. The output signal is amplified by an instrumentation amplifier interfacing the sensor with the Raspberry Pi controller (see electronics). This sensor is a wear part with a lifespan of about 6 years under operation at ambient air; therefore under continuous ventilator operation with oxygen-enriched gas, it will need to be replaced more frequently. This part can be replaced with any other medical O2 sensor provided calibration is performed given that these parts are typically sold as raw sensors, with a 3-pin molex interface. Moreover, the sensor we specify is compatible with a range of medical O2 sensors, including the Analytical Industries PSR-11-917-M or the Puritan Bennett 4-072214-00, so we anticipate abundant sourcing options.
Airway pressure sensor. We selected a pressure sensor with a few key characteristics in mind: 1) the sensor had to be compatible with the 5V supply of the Raspberry Pi, 2) the sensor’s input pressure range had conform to the range of pressures possible in our device (up to 70 cm H2O, the pressure relief valve’s cutoff), and 3) the sensor’s response time had to be sufficiently fast. We selected the amplified middle pressure sensor from Amphenol (1 PSI-D-4V), which was readily available, with a measurement range up to 70 cm H2O and an analog output voltage span of 4 V. Moreover, the decision to utilize an analog sensor is convenient for engineers replicating the design, as new analog sensors can be swapped in without extensive code and electronics modifications, as in the case of I2C devices which require modifications to hardware addresses. Other pressure sensors from this Amphenol line can be used as replacements if necessary.
Spirometer. Because flow measurement is essential for measuring tidal volume during pressure-controlled ventilation, medical flow sensor availability was extremely limited during the early stages of the 2020 COVID-19 pandemic, and supply is still an issue. For that reason, we looked for inexpensive, more easily sourced spirometers to use in our system. We used the GE D-Lite spirometer, which is a mass-produced part and has been used in hospitals for nearly 30 years. The D-Lite sensor is inserted in-line with the flow of gas on the expiratory limb, and two ports are used to measure the differential pressure drop resulting from flow through a narrow physical restriction. The third pressure-measurement port on the D-Lite is blocked by a male Luer cap, but this could be used as a backup pressure measurement port if desired. An Amphenol 5 INCH-D2-P4V-MINI was selected to measure the differential pressure across the two D-Lite takeoffs. As with the primary (absolute) pressure sensor, this sensor was selected to conform to the voltage range of the Raspberry Pi, operate within a small pressure range, and have a sufficiently fast response time (partially as a function of the analog-to-digital converter). Also, this analog sensor can be readily replaced with a similar analog sensor without substantial code/electronics modifications.
Electronics¶

PVP block diagram for main electrical components¶
The PVP is coordinated by a Raspberry Pi 4 board, which runs the graphical user interface, administers the alarm system, monitors sensor values, and sends actuation commands to the valves. The core electrical system consists of two modular PCB ‘hats’, a sensor PCB and an actuator PCB, that stack onto the Raspberry Pi via 40-pin stackable headers. The modularity of this system enables individual boards to be revised or modified to adapt to component substitutions if required.
Power and I/O¶
The main power to the systems is supplied by a DIN rail-mounted 150W 24V supply, which drives the inspiratory valve (4W) and expiratory valves (13W). This voltage is converted to 5V by a switched mode PCB-mounted regulated to power the Raspberry Pi and sensors. This power is transmitted across the PCBs through the stacked headers when required.
Part |
Description |
---|---|
Meanwell 24 V DC Power Supply |
DIN Rail Power Supplies 150W 24V 5A EN55022 Class B |
Raspberry Pi |
Raspberry Pi- Model B-1 (1GB RAM) |
USB-C Charger/cable |
To power the RPi |
Micro SD Card |
SanDisk Ultra 32GB MicroSDHC UHS-I Card with Adapter |
Raspberry Pi Display |
Matrix Orbital: TFT Displays & Accessories 7 in HDMI TFT G Series |
HDMI for Display |
Display cable: HDMI Cables HDMI Cbl Assbly 1M Micro to STD |
Mini USB for Display |
Display cable: USB Cables / IEEE 1394 Cables 3 ft Ext A-B Mini USB Cable |
Screen mount thumb screws |
SCREEN_MOUNT_THUMB_SCREW: Brass Raised Knurled-Head Thumb Screw, 1/4”-20 Thread Size, 1/2” Long |
Cable grommet |
USER_INTERFACE_CABLE_GROMMET: Buna-N Rubber Grommets, for 1-3/8” Hole Diameter and 1/16” Material Thickness, 1” ID, pack of 10 |
Cable P-clip |
USER_INTERFACE_CABLE_P-CLIP_0.375_ID_SS: Snug-Fit Vibration-Damping Loop Clamp, 304 Stainless Steel with Silicone Rubber Cushion, 3/8” ID, pack of 10, 17/64 mounting holes |
Keyboard |
Adesso: Mini keyboard with trackball |
Sensor PCB¶
The sensor board interfaces four analog output sensors with the Raspberry Pi via I2C commands to a 12-bit 4-channel ADC (Adafruit ADS1015).
an airway pressure sensor (Amphenol 1 PSI-D-4V-MINI)
a differential pressure sensor (Amphenol 5 INCH-D2-P4V-MINI) to report the expiratory flow rate through a D-Lite spirometer
an oxygen sensor (Sensiron SS-12A) whose 13 mV differential output signal is amplified 250-fold by an instrumentation amplifier (Texas Instruments INA126)
a fourth auxiliary slot for an additional analog output sensor (unused)
A set of additional header pins allows for digital output sensors (such as the Sensiron SFM3300 flow sensor) to be interfaced with the Pi directly via I2C if desired.

Sensor PCB schematic¶
Ref |
Part |
Purpose |
---|---|---|
J1 |
40-pin stackable RPi header |
Connects board to RPi |
J2 |
4-pin 0.1” header |
I2C connector if desired |
J3 |
2-pin 0.1” header |
Connects ALRT pin from ADS1115 to RPi if needed |
J4 |
3-pin 0.1” header or 3 pin fan extension cable |
Connects board to oxygen sensor |
R1 |
330 Ohm resistor |
Sets gain for INA126 |
C1 |
10 uF, 25V |
Cap for TL7660 |
C2 |
10 uF, 25V |
Cap for TL7660 |
U1 |
TL7660, DIP8 |
Rail splitter for INA126 |
U2 |
INA126, DIP8 |
Instrumentation amplifier for oxygen sensor output |
U3 |
Amphenol 5 INCH-D2-P4V-MINI |
Differential pressure sensor (for flow measurement) |
U4 |
Adafruit ADS1115 |
4x 12-bit ADC |
U5 |
Amphenol 1 PSI-D-4V-MINI |
Airway pressure sensor |
U6 |
Auxiliary analog output sensor slot |
Actuator PCB¶
The purpose of the actuator board is twofold:
regulate the 24V power supply to 5V (CUI Inc PDQE15-Q24-S5-D DC-DC converter)
interface the Raspberry Pi with the inspiratory and expiratory valves through an array of solenoid drivers (ULN2003A Darlington transistor array)

Actuator PCB schematic¶
Ref |
Part |
Purpose |
---|---|---|
J2 |
2-pin screw terminal, 5.08 mm pitch, PCB mount |
Connects to 24V supply |
J3 |
2-pin screw terminal, 5.08 mm pitch, PCB mount |
Connects to on/off expiratory valve |
J4 |
2-pin screw terminal, 5.08 mm pitch, PCB mount |
Connects to inspiratory valve, driven by PWM |
J5 |
40-pin stackable RPi header |
Connects board to RPi |
J6 |
2-pin 0.1” header |
Jumper between 5V and Raspberry Pi |
C1 |
100 uF, 16V |
5V rail filter cap |
C2 |
6.8 uF, 50V |
24V rail filter cap |
C3 |
6.8 uF, 50V |
24V rail filter cap |
U1 |
ULN2003A |
Darlington BJT array to drive solenoids |
U2 |
CUI PDQ15-Q24-S5-D |
24-to-5V DC-DC converter |
Bill of Materials¶
Component |
Description |
McMaster Part # (if applicable) |
Category |
Unit Qty |
Pkg. Qty |
Bulk Qty |
Bulk Parts Cost |
Unit Cost |
Subtotal (Bulk Parts Cost) |
Subtotal (Unit Parts Cost) |
Sourcing Link |
|
---|---|---|---|---|---|---|---|---|---|---|---|---|
I/O |
||||||||||||
Raspberry Pi |
Raspberry Pi- Model B-1 (1GB RAM) |
Compute |
1 |
1 |
1 |
$53.00 |
$53.00 |
$53.00 |
$53.00 |
|||
USB-C Charger/cable |
To power the RPi |
Compute |
1 |
1 |
1 |
$9.88 |
$9.88 |
$9.88 |
$9.88 |
|||
Micro SD Card |
SanDisk Ultra 32GB MicroSDHC UHS-I Card with Adapter |
Compute |
1 |
1 |
1 |
$8.29 |
$8.29 |
$8.29 |
$8.29 |
https://www.amazon.com/dp/B073JWXGNT/ref=twister_B07B3MFBHY?_encoding=UTF8&th=1 |
||
7” Display |
Lilliput 779GL-70NP/C/T - 7” HDMI Capacitive Touchscreen monitor |
Compute |
1 |
1 |
1 |
$249.18 |
$249.18 |
$249.18 |
$249.18 |
https://lilliputdirect.com/lilliput-779gl-capacitive-touchscreen-hdmi-monitor?search=779gl |
||
Screen Mount |
Lilliput Monitor Stand |
Compute |
1 |
1 |
1 |
$6.00 |
$6.00 |
$6.00 |
$6.00 |
|||
HDMI for Display |
Display cable: HDMI Cables HDMI Cbl Assbly 1M Micro to STD |
Compute |
1 |
1 |
1 |
$19.59 |
$19.59 |
$19.59 |
$19.59 |
|||
Mini USB for Display |
Display cable: USB Cables / IEEE 1394 Cables 3 ft Ext A-B Mini USB Cable |
Compute |
1 |
1 |
1 |
$5.49 |
$5.49 |
$5.49 |
$5.49 |
|||
Cable grommet |
USER_INTERFACE_CABLE_GROMMET: Buna-N Rubber Grommets, for 1-3/8” Hole Diameter and 1/16” Material Thickness, 1” ID, pack of 10 |
9307K61 |
Routing |
1 |
10 |
1 |
$10.08 |
$1.01 |
$10.08 |
$1.01 |
||
Cable P-clip |
USER_INTERFACE_CABLE_P-CLIP_0.375_ID_SS: Snug-Fit Vibration-Damping Loop Clamp, 304 Stainless Steel with Silicone Rubber Cushion, 3/8” ID, pack of 10, 17/64 mounting holes |
3225T63 |
Routing |
2 |
10 |
1 |
$7.00 |
$0.70 |
$7.00 |
$1.40 |
||
Keyboard |
Adesso: Mini keyboard with trackball |
Compute |
1 |
1 |
1 |
$49.99 |
$49.99 |
$49.99 |
$49.99 |
https://m.cdw.com/product/adesso-easy-touch-mini-trackball-keyboard/1839486 |
||
AC Power Cable |
Compute |
1 |
1 |
1 |
$8.15 |
$8.15 |
$8.15 |
$8.15 |
||||
Compute Cost |
$419 |
|||||||||||
Airway Components |
||||||||||||
CUSTOM_ID_RUBBER_GROMMET_FOR_0.5_HOLE_0.0625_MATL: Cut-to-Size Grommets, SBR Rubber, for 1/2” Hole and 1/16” Thickness, 1/64-3/8” ID, Pack of 10 |
2633N11 |
Routing |
1 |
10 |
1 |
$10.00 |
$1.00 |
$10.00 |
$1.00 |
www.mcmaster.com/2633N11/ |
||
Oxygen DISS to 3/8” NPT Adapter |
(Not specified in assembly) |
Tubing/Adapters |
||||||||||
Inlet manifold |
1023N150_MANIFOLD_0.375_NPT_IN_0.25NPT_OUT : Straight-Flow Rectangular Manifold, 2 Outlets, 3/8 NPT Inlet x 1/4 NPT Outlet |
1023N15 |
Tubing/Adapters |
1 |
1 |
1 |
$24.56 |
$24.56 |
$24.56 |
$24.56 |
||
1/4” NPT Connector (male/male) |
PIPE_STUB_0.25_NPT_SS_W_SEALANT: Standard-Wall 304/304L Stainless Steel Pipe Nipple with Sealant, Threaded on Both Ends, 1/4 NPT, 1-1/2” Long |
1455N133 |
Tubing/Adapters |
1 |
1 |
1 |
$3.89 |
$3.89 |
$3.89 |
$3.89 |
||
Teflon tape |
PTFE thread sealant tape (1/2”) |
6802K12 |
Tubing/Adapters |
1 |
1 |
1 |
$2.62 |
$2.62 |
$2.62 |
$2.62 |
||
Manifold plug 1/4” NPT |
MANIFOLD_PLUG_0.25_NPT_SS: High-Pressure 347 Stainless Steel Pipe Fitting, Plug with Hex Drive, 1/4 NPT Male |
1725K13 |
Tubing/Adapters |
1 |
1 |
1 |
$4.50 |
$4.50 |
$4.50 |
$4.50 |
||
Hex nuts |
LN_10-32_STAINLESS_18-8: 18-8 Stainless Steel Hex Nut, 10-32 Thread Size, pack of 100 |
91841A195 |
Fixing and framing |
100 |
1 |
$3.77 |
$0.04 |
$3.77 |
$0.00 |
|||
Manifold washers |
W_10_NARROW_0.406OD_316_SS: 18-8 Stainless Steel Washer for Soft Material, Number 10 Screw Size, Narrow ASME Designation, pack of 25 |
92217A440 |
Fixing and framing |
25 |
1 |
$10.16 |
$0.41 |
$10.16 |
$0.00 |
|||
Fixed pressure regulator |
Fixed-Pressure Compressed Air Regulator (with two plugs) , 50 psi |
1777K41 |
Flow control |
1 |
1 |
1 |
$47.94 |
$47.94 |
$47.94 |
$47.94 |
||
Push-to-connect: 1/4” NPT |
PUSH_CONNECT_8MM_0.25_NPTM: 1/4” NPT to push-to-connect 8mm tubing |
5225K715 |
Tubing/Adapters |
1 |
2 |
$5.48 |
$5.48 |
$10.96 |
$0.00 |
|||
Pneumatic Tubing |
Polyurethane tubing: Firm Polyurethane Rubber Tubing for Air and Water, 5 mm ID, 8 mm OD |
50315K71 |
Tubing/Adapters |
3 |
1 |
3 |
$0.71 |
$0.71 |
$2.13 |
$2.13 |
||
Push-to-connect: 1/8” BSPT |
PUSH_CONNECT_FITTING_8MM_TO_0.125_BPST: Push-to-Connect Tube Fitting for Air, Straight Adapter, 8 mm Tube OD x 1/8 BSPT Male |
5225K305 |
Tubing/Adapters |
2 |
1 |
2 |
$5.38 |
$5.38 |
$10.76 |
$10.76 |
||
Insp proportional valve |
SMC PVQ31-5G-23-01N-H valve, proportional, PVQ PROPORTIONAL VALVE |
Flow control |
1 |
1 |
1 |
$107.10 |
$107.10 |
$107.10 |
$107.10 |
https://www.ocpneumatics.com/smc-pvq31-5g-23-01n-h-valve-proportional-pvq-proportional-valve/ |
||
Push-to-connect: Inline T-adapter to 1/4” NPT |
PUSH_CONNECT_T_8MM_0.25_NPT: Push-to-Connect Tube Fitting for Air, Inline Tee Adapter, for 8 mm Tube OD x 1/4 NPT Male |
5225K806 |
Tubing/Adapters |
1 |
1 |
1 |
$7.58 |
$7.58 |
$7.58 |
$7.58 |
||
1/4” NPT Connector (female/female) |
NPTF_0.25_X_0.25_NPTF_COUPLER: CPVC Pipe Fitting for Hot Water, Straight Connector, 1/4 NPT Thread Female |
4589K58 |
Tubing/Adapters |
1 |
1 |
1 |
$9.84 |
$9.84 |
$9.84 |
$9.84 |
||
Pressure release valve |
Compact Nylon Pressure-Relief Valve for Air, 1 psi, 1/4 NPT Male |
4277T52 |
Tubing/Adapters |
1 |
1 |
1 |
$19.89 |
$19.89 |
$19.89 |
$19.89 |
||
Oxygen sensor |
O2 Sensor, SS-12A (or equivalents, e.g. Analytical Industries PSR-11-917-M) |
Sensors |
1 |
1 |
1 |
$59.00 |
$59.00 |
$59.00 |
$59.00 |
https://www.sensoronics.com/products/ss-12a-replaces-teledyne-r22-msa-472062 |
||
Emergency breathing (check) valve |
Teleflex Medical One Way Valve |
Flow control |
1 |
1 |
1 |
$1.05 |
$1.05 |
$1.05 |
$1.05 |
https://serfinitymedical.com/products/teleflex-medical-one-way-valve-teleflex-medical-1665 |
||
DAR filters |
DAR Electrostatic filters - Large (10x) (NEED A PRESCRIPTION TO ORDER) |
Yes |
Tubing/Adapters |
2 |
1 |
2 |
$1.72 |
$1.72 |
$3.44 |
$3.44 |
||
Adult Respiratory Circuit w Humidifier Limb |
Standard resp. circuit |
Yes |
Tubing/Adapters |
1 |
1 |
1 |
$13.00 |
$13.00 |
$13.00 |
$13.00 |
Circuit: https://orsupply.com/product/1505 |
|
Adapter / filter to connect gas sampling lines to proximal side - TOM |
Tubing/Adapters |
#DIV/0! |
$0.00 |
|||||||||
Gas sampling line |
Gas Sampling Line McKesson 16-GSL Each/1 (x3) |
Tubing/Adapters |
3 |
1 |
3 |
$1.67 |
$1.67 |
$5.01 |
$5.01 |
|||
Luer lock connector |
Sensit Luer Lock Connector from Grainger |
Tubing/Adapters |
2 |
1 |
2 |
$10.25 |
$10.25 |
$20.50 |
$20.50 |
|||
Luer lock filter |
Luer lock filter 0.2um from Promepla |
https://catalog.promepla.com/promepla-component/ecb15992 / https://catalog.promepla.com/uploads/medias/plan-ecb15992.pdf |
||||||||||
Pressure sensor (for Paw) |
1 PSI-D-4V-MINI |
Sensors |
1 |
1 |
1 |
$32.00 |
$32.00 |
$32.00 |
$32.00 |
|||
Tubing connectors |
Flexible Connector (Silicone Rubber) 22mm I.D. X 22mm I.D. (x6) |
Tubing/Adapters |
2 |
1 |
2 |
$7.00 |
$7.00 |
$14.00 |
$14.00 |
|||
Flow sensor |
Disposable GE D-Lite flow sensor |
Tubing/Adapters |
1 |
1 |
1 |
$4.76 |
$4.76 |
$4.76 |
$4.76 |
https://jakenmedical.com/supplies-accessories/disposable-d-lite-sensor-50-box-896952/ |
||
Luer plug |
Plastic Quick-Turn Tube Coupling, Nylon Plastic Caps for Plugs |
51525K311 |
Tubing/Adapters |
1 |
10 |
1 |
$2.60 |
$0.26 |
$2.60 |
$0.26 |
||
Differential pressure sensor for flow |
5 INCH-D2-P4V-MINI |
Sensors |
1 |
1 |
1 |
$52.70 |
$52.70 |
$52.70 |
$52.70 |
|||
3/4” NPT Connector (male/male) |
Thick-Wall Polypropylene Pipe Fitting for Chemicals, Connector with Hex Body, 3/4 NPT Male |
46825K31 |
Tubing/Adapters |
2 |
1 |
2 |
$1.53 |
$1.53 |
$3.06 |
$3.06 |
||
Exp solenoid valve |
SMC VXZ250HGB valve, 20A, NORMALLY OPEN, C37, Port 3/4 (NPT), Orifice 20 |
Flow control |
1 |
1 |
1 |
$115.10 |
$115.10 |
$115.10 |
$115.10 |
|||
Exp solenoid brackets |
T-Slotted Framing, Silver Flush 90 Degree Angle Bracket for 1” High Rail |
3136N157 |
Fixing and framing |
2 |
1 |
2 |
$11.22 |
$11.22 |
$22.44 |
$22.44 |
||
Framing/Structural Components |
||||||||||||
80/20 (x4) |
T-Slotted Framing, Single 4-Slot Rail, Silver, 1” High x 1” Wide, Solid, 3’ Long - 4x |
47065T101 |
Fixing and framing |
4 |
1 |
4 |
$10.57 |
$10.57 |
$42.28 |
$42.28 |
||
80/20 gusset bracket (x18) |
Silver Gusset Bracket, 1” Long for 1” High Rail T-Slotted Framing x18 |
47065T663 |
Fixing and framing |
18 |
1 |
18 |
$6.54 |
$6.54 |
$117.72 |
$117.72 |
||
80/20 rail (x6) |
T-Slotted Framing, Single 4-Slot Rail, Silver, 1” High x 1” Wide, Solid, 1’ Long x6 |
47065T101 |
Fixing and framing |
6 |
1 |
6 |
$5.84 |
$5.84 |
$35.04 |
$35.04 |
||
80/20 button head nuts (packs of 25, x2) |
End-feed single nuts for T-Slotted framing: T-Slotted Framing, End-Feed Single Nut, 1/4”-20 Thread |
47065T905 |
Fixing and framing |
25 |
2 |
$5.62 |
$0.22 |
$11.24 |
$0.00 |
|||
Button-head hex screws, pack of 50 |
BHCS_0.25-20x0.412_SS: 18-8 Stainless Steel Button Head Hex Drive Screw, 1/4”-20 Thread Size, 7/16” Long, Pack of 50 |
92949A833 |
Fixing and framing |
50 |
1 |
$6.03 |
$0.12 |
$6.03 |
$0.00 |
|||
Socket head screw, pack of 100 |
SHCS_M3_X_0.5_6MM_SS: 18-8 Stainless Steel Socket Head Screw, M3 x 0.5 mm Thread, 6 mm Long, pack of 100 |
91292A111 |
Fixing and framing |
100 |
1 |
$9.66 |
$0.10 |
$9.66 |
$0.00 |
|||
Button head screws (long), pack of 10 |
BHCS_10-32_X_2_SS: 18-8 Stainless Steel Button Head Hex Drive Screw, 10-32 Thread Size, 2” Long, pack of 10 |
92949A275 |
Fixing and framing |
10 |
1 |
$2.65 |
$0.27 |
$2.65 |
$0.00 |
|||
Button head screws (short), pack of 25 |
BHCS_M6_X_1_8MM_SS: Button Head Hex Drive Screw, Passivated 18-8 Stainless Steel, M6 x 1 mm Thread, 8mm Long, pack of 25 |
92095A239 |
Fixing and framing |
25 |
1 |
$8.40 |
$0.34 |
$8.40 |
$0.00 |
|||
HDPE sheeting |
Moisture-Resistant HDPE Sheet, 12” x 48” x 1/16” |
8619K423 |
Fixing and framing |
2 |
1 |
2 |
$10.64 |
$10.64 |
$21.28 |
$21.28 |
||
Lifting handle |
LIFTING_HANDLE: Oval Pull Handle, Unthreaded Hole, Black Aluminum, 4-9/16” Center-to-Center Width |
5190A21 |
Fixing and framing |
2 |
1 |
2 |
$14.38 |
$14.38 |
$28.76 |
$28.76 |
||
Screws for lifting handle |
SHCS_0.25-20x0.75_Gr8_ASTM_F1136, pack of 50 |
91274A164 |
Fixing and framing |
50 |
1 |
$9.69 |
$0.19 |
$9.69 |
$0.00 |
|||
Leveling mount (4 feet) |
TLA_LEVELING_MOUNT: Light Duty Leveling Mount, 1” Long 1/4”-20 Threaded Stud, pack of 4 |
23015T82 |
Fixing and framing |
1 |
4 |
1 |
$1.89 |
$0.47 |
$1.89 |
$0.47 |
||
Nuts for leveling mount |
LN_THIN_0.25-20_STAINLESS: 18-8 Stainless Steel Thin Hex Nut, 1/4”-20 Thread Size, pack of 100 |
91847A029 |
Fixing and framing |
100 |
1 |
$4.41 |
$0.04 |
$4.41 |
$0.00 |
|||
PETG for 3d printing |
~1 kg spool of PETG |
Tubing/Adapters |
1 |
1 |
1 |
$30.00 |
$30.00 |
$30.00 |
$30.00 |
|||
Structural |
$329 |
|||||||||||
Electronics |
||||||||||||
Standoffs |
Standoffs & Spacers 4.5 HEX 12.0mm ALUM |
Electronics |
50 |
$0.45 |
$22.50 |
$0.00 |
||||||
Meanwell DC Power Supply |
DIN Rail Power Supplies 150W 24V 5A EN55022 Class B |
Electronics |
1 |
1 |
1 |
$30.66 |
$30.66 |
$0.00 |
||||
DIN Rail |
Aluminum DIN 3 Rail, 10mm Deep, 1m Long |
8961K17 |
Electronics |
1 |
1 |
1 |
$7.74 |
$7.74 |
$0.00 |
|||
DIN washers |
W_0.25_FLAT_THICK_GR8_YELLOW_ZINC: Zinc Yellow-Chromate Plated Steel Oversized Washer, Grade 8, for 1/4” Screw Size, 0.281” ID, 0.625” OD, package of 100 |
98025A029 |
Electronics |
6 |
100 |
1 |
$13.94 |
$13.94 |
$0.00 |
|||
Sensor board (excluding pressure sensors) |
||||||||||||
2-layer PCB |
Advanced Circuits 2 layer board |
Electronics |
1 |
3 |
3 |
$33.00 |
$99.00 |
$33.00 |
https://www.4pcb.com/pcb-prototype-2-4-layer-boards-specials.html |
|||
12-bit ADC |
Adafruit Data Conversion IC Development Tools ADS1015 Breakout 12-Bit ADC |
Electronics |
1 |
1 |
1 |
$9.95 |
$10.00 |
$9.95 |
https://www.mouser.com/ProductDetail/Adafruit/1083?qs=GURawfaeGuCmI2li4B6pKg%3D%3D |
|||
Voltage splitter U1 |
TL7660, 8-DIP package |
Electronics |
1 |
1 |
1 |
$1.55 |
$2.00 |
$1.55 |
https://www.digikey.com/product-detail/en/texas-instruments/TL7660CP/296-21857-5-ND/1629031 |
|||
Instrumentation amplifier U2 |
INA126PA, 8-DIP package |
Electronics |
1 |
1 |
1 |
$3.18 |
$4.00 |
$3.18 |
https://www.digikey.com/product-detail/en/texas-instruments/INA126PA/INA126PA-ND/300992 |
|||
R3, sensor |
$0.00 |
|||||||||||
40 pin Pi header J1 |
Raspberry pi 40 pin stacking header |
Electronics |
1 |
1 |
1 |
$2.95 |
$3.00 |
$2.95 |
https://www.digikey.com/product-detail/en/adafruit-industries-llc/1979/1528-1783-ND/6238003 |
|||
4x1 header J2 |
male pin header 1x04 P2.54mm |
Electronics |
360 pins |
4 pins |
1 |
$4.95 |
$5.00 |
$0.06 |
https://www.digikey.com/product-detail/en/adafruit-industries-llc/4150/1528-2922-ND/10123801 |
|||
2x1 header J3 |
male pin header 1x02 P2.54mm |
Electronics |
360 pins |
2 pins |
1 |
$4.95 |
$5.00 |
$0.03 |
||||
Oxygen sensor cable (J4) |
3 pin fan cable extension, 36”, ribbon style |
Electronics |
1 |
$1.99 |
$2.00 |
$1.99 |
https://www.coolerguys.com/products/3-pin-fan-cable-extension-12-thru-72-inches?variant=17666421509 |
|||||
330 Ohm resistor R1 |
330 Ohm resistor, through hole, 1/4 Watt |
Electronics |
1 |
1 |
1 |
$0.10 |
$1.00 |
$0.10 |
||||
Capacitors C1,C2 |
10 uF, 25V, electrolytic radial |
Electronics |
1 |
1 |
1 |
$0.21 |
$1.00 |
$0.21 |
||||
Actuator board |
||||||||||||
2-layer PCB |
Advanced Circuits 2 layer board |
Electronics |
1 |
3 |
3 |
$33.00 |
$99.00 |
$33.00 |
https://www.4pcb.com/pcb-prototype-2-4-layer-boards-specials.html |
|||
5V DC/DC Converter |
490-PDQE15-Q24-S5-D |
Electronics |
1 |
1 |
1 |
$19.12 |
$20.00 |
$19.12 |
||||
Darlington array U1 |
ULN2003AN IC PWR RELAY 7NPN 1:1 16DIP |
Electronics |
1 |
1 |
2 |
$0.66 |
$2.00 |
$1.32 |
https://www.mouser.com/ProductDetail/Texas-Instruments/ULN2003AN?qs=FOlmdCx%252BAA1wYQ1G8c8hpQ%3D%3D |
|||
40 pin Pi header J1 |
Raspberry pi 40 pin stacking header |
Electronics |
1 |
1 |
1 |
$2.95 |
$3.00 |
$2.95 |
https://www.digikey.com/product-detail/en/adafruit-industries-llc/1979/1528-1783-ND/6238003 |
|||
Screw terminals J2, J3, J4 |
5.08 mm pitch 2-pin screw terminal block connector, PCB mount |
Electronics |
1 |
1 |
3 |
$1.04 |
$4.00 |
$3.12 |
https://www.digikey.com/product-detail/en/te-connectivity-amp-connectors/282837-2/A113320-ND/2187973 |
|||
2x1 header J5 |
male pin header 1x02 P2.54mm |
Electronics |
360 pins |
2 pins |
1 |
$4.95 |
$5.00 |
$0.03 |
||||
Capacitor C1 |
100 uF, 16V, electrolytic radial |
Electronics |
1 |
1 |
1 |
$0.32 |
$1.00 |
$0.32 |
||||
Capacitors C2,C3 |
6.8 uF, 50V, electrolytic radial |
Electronics |
1 |
1 |
2 |
$0.20 |
$1.00 |
$0.40 |
||||
Speaker |
Logitech Z50 speaker |
Electronics |
1 |
1 |
1 |
$19.99 |
$20.00 |
|||||
Electronics |
$342 |
|||||||||||
Total Cost |
$1,741.90 |
$1,388.93 |
||||||||||
Specialized Tools |
||||||||||||
M16 tap |
HSS Tap, Bottoming Chamfer, M16 x 1.0 mm Thread (for mounting O2 Sensor) |
26015A236 |
Tools and testing |
1 |
$46.63 |
$47.00 |
||||||
1/4” NPT tap |
Uncoated High-Speed Steel Pipe and Conduit Thread Tap High-Speed Steel, Plug Chamfer, 1/4 NPT, 2-7/16” Overall Length |
2525A173 |
Tools and testing |
1 |
$24.94 |
$25.00 |
||||||
3/4” NPT tap |
Uncoated High-Speed Steel Pipe and Conduit Thread Tap: High-Speed Steel, Plug Chamfer, 3/4 NPT, 3-1/4” Overall Length |
2525A176 |
Tools and testing |
1 |
$70.16 |
$71.00 |
||||||
Ingmar QuickLung Test Bellows |
(Optional) variable resistance/compliance test lung for testing only |
Tools and testing |
1 |
$1,500.00 |
$1,500.00 |
|||||||
Not on the List: |
||||||||||||
PETG for 3d printing |
~1 kg spool of PETG |
Tubing/Adapters |
1 |
$30.00 |
$30.00 |
|||||||
Commercial PEEP valves |
Alternative: Medium silicone film for printed PEEP valve: https://www.mcmaster.com/86045K58 |
Flow control |
||||||||||
Adult Test Lung |
Standard adult test lung (Many options here) |
Tools and testing |
$30.00 |
|||||||||
Air compressor for initial testing |
||||||||||||
Software Overview¶

PVP is modularly designed to facilitate future adaptation to new hardware configurations and ventilation modes. APIs were designed for each of the modules to a) make them easily inspected and configured and b) make it clear to future developers how to adapt the system to their needs.
PVP runs as multiple independent processes The GUI provides an interface to control and monitor ventilation, and the controller process handles the ventilation logic and interfaces with the hardware. Inter-process communication is mediated by a coordinator module via xml-rpc. Several ’common’ modules facilitate system configuration and constitute the inter-process API. We designed the API around a uni-fied, configurablevaluesmodule that allow the GUI andcontroller to be reconfigured while also ensuring system robustness and simplicity.
The GUI and Coordinator run in the first process, receive user input, display system status, and relay
ControlSetting
s to the Controller .At launch, the Coordinator spawns a Controller that runs the logic of the ventilator based on control values from the GUI.
The Controller communicates with a third pigpiod process which communicates with the ventilation hardware
PVP is configured by
The Values module parameterizes the different sensor and control values displayed by the GUI and used by the controller
The Prefs module creates a
prefs.json
file in~/pvp
that defines user-specific preferences.
PVP is launched like:
python3 -m pvp.main
And launch options can be displayed with the --help
flag.
PVP Modules¶
GUI
A modular GUI with intuitive controls and a clear alarm system that can be configured to control any parameter or display values from any sensor.
Controller
A module to handle the ventilation logic, build around a gain-adjusted PID controller.
IO
A hardware abstraction layer powered by pigpio that can read/write at >300 Hz
Alarm
Define complex and responsive alarm triggering criteria with human-readable Alarm Rules
Common
Modules that provide the API between the GUI and controller, user preferences, and other utilities
GUI¶
Main GUI Module¶
Classes:
|
Representation of alarm status and parameters |
|
An enumeration. |
|
An enumeration. |
|
The Alarm Manager |
|
Message containing ventilation control parameters. |
|
|
|
The Main GUI window. |
|
Structured class for communicating sensor readings throughout PVP. |
|
Canonical names of all values used in PVP. |
Functions:
|
Retreive the currently running instance of the GUI |
|
Initialize a logger for logging events. |
|
Launch the GUI with its appropriate arguments and doing its special opening routine |
|
module function to return a |
|
Store the current instance of the GUI |
-
class
pvp.gui.main.
PVP_Gui
(coordinator: pvp.coordinator.coordinator.CoordinatorBase, set_defaults: bool = False, update_period: float = 0.05, screenshot=False)[source]¶ The Main GUI window.
Creates 5 sets of widgets:
A
Control_Panel
in the top left corner that controls basic system operation and settingsA
Alarm_Bar
along the top that displays active alarms and allows them to be dismissed or mutedA column of
Display
widgets (according tovalues.DISPLAY_MONITOR
) on the left that display sensor values and control their alarm limitsA column of
Plot
widgets (according tovalues.PLOTS
) in the center that display waveforms of sensor readingsA column of
Display
widgets (according tovalues.DISPLAY_CONTROL
) that control ventilation settings
Continually polls the
coordinator
withupdate_gui()
to receive newSensorValues
and dispatch them to display widgets, plot widgets, and the alarm managerNote
Only one instance can be created at a time. Uses
set_gui_instance()
to store a reference to itself. after initialization, use get_gui_instance to retrieve a reference.- Parameters
coordinator (
CoordinatorBase
) – Used to communicate with theControlModuleBase
.set_defaults (bool) – Whether default Value s should be set on initialization (default
False
) – used for testing and development, values should be set manually for each patient.update_period (float) – The global delay between redraws of the GUI (seconds), used by
timer
.screenshot (bool) – Whether alarms should be manually raised to show the different alarm severities, only used for testing and development and should never be used in a live system.
-
monitor
¶ Dictionary mapping
values.DISPLAY_MONITOR
keys towidgets.Display
objects- Type
-
controls
¶ Dictionary mapping
values.DISPLAY_CONTROL
keys towidgets.Display
objects- Type
-
plot_box
¶ Container for plots
- Type
Plot_Box
-
coordinator
¶ Some coordinator object that we use to communicate with the controller
-
alarm_manager
¶ Alarm manager instance
- Type
-
timer
¶ Timer that calls
PVP_Gui.update_gui()
- Type
PySide2.QtCore.QTimer
-
start_time
¶ Start time as returned by
time.time()
- Type
-
logger
¶ Logger generated by
loggers.init_logger()
Attributes:
CONTROL
MONITOR
PLOTS
control_width
controls_set
Check if all controls are set
gui_closing
(*args, **kwargs)monitor_width
plot_width
state_changed
(*args, **kwargs)staticMetaObject
total_width
update_period
The global delay between redraws of the GUI (seconds)
Methods:
_screenshot
()Raise each of the alarm severities to check if they work and to take a screenshot
_set_cycle_control
(value_name, new_value)Compute the computed breath cycle control.
closeEvent
(event)Emit
gui_closing
and close!handle_alarm
(alarm)Receive an
Alarm
from theAlarm_Manager
init_controls
()on startup, set controls in coordinator to ensure init state is synchronized
init_ui
()Create the UI components for the ventilator screen
init_ui_controls
()Create the “controls” column of
widgets.Display
widgets
init_ui_monitor
()Create the left “sensor monitor” column of
widgets.Display
widgets
init_ui_plots
()Create the
Plot_Container
init_ui_signals
()Connect Qt signals and slots between widgets
init_ui_status_bar
()Create the
widgets.Control_Panel
andwidgets.Alarm_Bar
limits_updated
(control)Receive updated alarm limits from the
Alarm_Manager
load_state
(state)Load GUI state and reconstitute
save_state
()Try to save GUI state to
prefs['VENT_DIR"] + prefs['GUI_STATE_FN']
set_breath_detection
(breath_detection)Connected to
breath_detection_button
- toggles autonomous breath detection in the controllerset_control
(control_object)Set a control in the alarm manager, coordinator, and gui
set_pressure_units
(units)Select whether pressure units are displayed as “cmH2O” or “hPa”
set_value
(new_value[, value_name])Set a control value using a value and its name.
start
()Click the
start_button
toggle_cycle_widget
(button)Set which breath cycle control is automatically calculated
toggle_lock
(state)Toggle the lock state of the controls
toggle_start
(state)Start or stop ventilation.
update_gui
([vals])- param vals
Default None, but SensorValues can be passed manually – usually for debugging
update_state
(state_type, key, val)Update the GUI state and save it to disk with
Vent_Gui.save_state()
-
gui_closing
(*args, **kwargs) = <PySide2.QtCore.Signal object>¶ PySide2.QtCore.Signal
emitted when the GUI is closing.
-
state_changed
(*args, **kwargs) = <PySide2.QtCore.Signal object>¶ PySide2.QtCore.Signal
emitted when the gui is started (True) or stopped (False)
-
MONITOR
= OrderedDict([(<ValueName.PIP: 1>, <pvp.common.values.Value object>), (<ValueName.PEEP: 3>, <pvp.common.values.Value object>), (<ValueName.BREATHS_PER_MINUTE: 5>, <pvp.common.values.Value object>), (<ValueName.INSPIRATION_TIME_SEC: 6>, <pvp.common.values.Value object>), (<ValueName.PRESSURE: 10>, <pvp.common.values.Value object>), (<ValueName.VTE: 9>, <pvp.common.values.Value object>), (<ValueName.FLOWOUT: 11>, <pvp.common.values.Value object>), (<ValueName.FIO2: 8>, <pvp.common.values.Value object>)])¶ Values to create
Display
widgets for in the Sensor Monitor column. Seevalues.DISPLAY_MONITOR
-
CONTROL
= OrderedDict([(<ValueName.PIP: 1>, <pvp.common.values.Value object>), (<ValueName.PEEP: 3>, <pvp.common.values.Value object>), (<ValueName.BREATHS_PER_MINUTE: 5>, <pvp.common.values.Value object>), (<ValueName.INSPIRATION_TIME_SEC: 6>, <pvp.common.values.Value object>), (<ValueName.IE_RATIO: 7>, <pvp.common.values.Value object>), (<ValueName.PIP_TIME: 2>, <pvp.common.values.Value object>)])¶ Values to create
Display
widgets for in the Control column. Seevalues.CONTROL
-
PLOTS
= OrderedDict([(<ValueName.PRESSURE: 10>, <pvp.common.values.Value object>), (<ValueName.FLOWOUT: 11>, <pvp.common.values.Value object>), (<ValueName.FIO2: 8>, <pvp.common.values.Value object>)])¶ Values to create
Plot
widgets for. Seevalues.PLOTS
-
monitor_width
= 3¶ Relative width of the sensor monitor column
-
plot_width
= 4¶ Relative width of the plot column
-
control_width
= 3¶ Relative width of the control column
-
total_width
= 10¶ computed from
monitor_width+plot_width+control_width
-
update_gui
(vals: pvp.common.message.SensorValues = None)[source]¶ - Parameters
vals (
SensorValue
) – Default None, but SensorValues can be passed manually – usually for debugging
-
init_ui
()[source]¶ Create the UI components for the ventilator screen
Call, in order:
Create and set sizes of major layouts
-
init_ui_status_bar
()[source]¶ Create the
widgets.Control_Panel
andwidgets.Alarm_Bar
and add them to the main layout
-
init_ui_monitor
()[source]¶ Create the left “sensor monitor” column of
widgets.Display
widgets
And add the logo to the bottom left corner if there’s room
-
init_ui_plots
()[source]¶ Create the
Plot_Container
-
init_ui_signals
()[source]¶ Connect Qt signals and slots between widgets
Connect controls and sensor monitors to
PVP_Gui.set_value()
Connect control panel buttons to their respective methods
-
set_value
(new_value, value_name=None)[source]¶ Set a control value using a value and its name.
Constructs a
message.ControlSetting
object to give toPVP_Gui.set_control()
Note
This method is primarily intended as a means of responding to signals from other widgets, Other cases should use
set_control()
- Parameters
new_value (float) – A new value for some control setting
value_name (
values.ValueName
) – THe ValueName for the control setting. IfNone
, assumed to be coming from aDisplay
widget that can identify itself with itsobjectName
-
set_control
(control_object: pvp.common.message.ControlSetting)[source]¶ Set a control in the alarm manager, coordinator, and gui
Also update our state with
update_state()
- Parameters
control_object (
message.ControlSetting
) – A control setting to give toCoordinatorBase.set_control
-
handle_alarm
(alarm: pvp.alarm.alarm.Alarm)[source]¶ Receive an
Alarm
from theAlarm_Manager
Alarms are both raised and cleared with this method – there is no separate “clear_alarm” method because an alarm of
AlarmSeverity
ofOFF
is cleared.Give the alarm to the
Alarm_Bar
and update the alarmDisplay.alarm_state
of all widgets listed asAlarm.cause
- Parameters
alarm (
Alarm
) – The alarm to raise (or clear)
-
limits_updated
(control: pvp.common.message.ControlSetting)[source]¶ Receive updated alarm limits from the
Alarm_Manager
When a value is set that has an
Alarm_Rule
thatAlarm_Rule.depends
on it, the alarm thresholds will be updated and handled here.Eg. the high-pressure alarm is set to be 15% above PIP. When PIP is changed, this method will receive a
message.ControlSetting
that tells us that alarm threshold has changed.Update the
Display
andPlot
widgets.If we are setting a new HAPA limit, that is also sent to the controller as it needs to respond as quickly as possible to high-pressure events.
- Parameters
control (
message.ControlSetting
) – A ControlSetting with itsmax_value
or
:param
min_value
set:
-
toggle_start
(state: bool)[source]¶ Start or stop ventilation.
Typically called by the
PVP_Gui.control_panel.start_button
.Raises a dialogue to confirm ventilation start or stop
Starts or stops the controller via the coordinator
If starting, locks controls.
- Parameters
state (bool) – If True, start ventilation. If False, stop ventilation.
-
closeEvent
(event)[source]¶ Emit
gui_closing
and close!Kill the coordinator with
CoordinatorBase.kill()
-
toggle_lock
(state)[source]¶ Toggle the lock state of the controls
Typically called by
PVP_Gui.control_panel.lock_button
- Parameters
state –
Returns:
-
update_state
(state_type: str, key: str, val: Union[str, float, int])[source]¶ Update the GUI state and save it to disk with
Vent_Gui.save_state()
Currently, just saves the state of control settings.
- Parameters
Returns:
-
load_state
(state: Union[str, dict])[source]¶ Load GUI state and reconstitute
currently, just
PVP_Gui.set_value()
for all previously saved values
-
staticMetaObject
= <PySide2.QtCore.QMetaObject object>¶
-
toggle_cycle_widget
(button)[source]¶ Set which breath cycle control is automatically calculated
The timing of a breath cycle can be parameterized with Respiration Rate, Inspiration Time, and Inspiratory/Expiratory ratio, but if two of these parameters are set the third is already known.
This method changes which value has its
Display
widget hidden and is automatically calculated- Parameters
button (
PySide2.QtWidgets.QAbstractButton
,values.ValueName
) – The Qt Button that invoked the method or else a ValueName
-
set_pressure_units
(units)[source]¶ Select whether pressure units are displayed as “cmH2O” or “hPa”
calls
Display.set_units()
on controls and plots that display pressure- Parameters
units (str) – one of “cmH2O” or “hPa”
-
set_breath_detection
(breath_detection: bool)[source]¶ Connected to
breath_detection_button
- toggles autonomous breath detection in the controller- Parameters
breath_detection (bool) – Whether the controller detects autonomous breaths and resets the breath cycle accordingly
-
_set_cycle_control
(value_name: str, new_value: float)[source]¶ Compute the computed breath cycle control.
We only actually have BPM and INSPt as controls, so if we’re using I:E ratio we have to compute one or the other.
Computes the value and calls
set_control()
with the appropriate values:# ie = inspt/expt # inspt = ie*expt # expt = inspt/ie # # cycle_time = inspt + expt # cycle_time = inspt + inspt/ie # cycle_time = inspt * (1+1/ie) # inspt = cycle_time / (1+1/ie)
-
property
controls_set
¶ Check if all controls are set
Note
Note that even when RR or INSPt are autocalculated, they are still set in their control objects, so this check is the same regardless of what is set to autocalculate
-
property
update_period
¶ The global delay between redraws of the GUI (seconds)
-
pvp.gui.main.
launch_gui
(coordinator, set_defaults=False, screenshot=False) → Tuple[PySide2.QtWidgets.QApplication, pvp.gui.main.PVP_Gui][source]¶ Launch the GUI with its appropriate arguments and doing its special opening routine
To launch the gui, one must:
Create a
PySide2.QtWidgets.QApplication
Set the app style using
gui.styles.DARK_THEME
Set the app palette with
gui.styles.set_dark_palette()
Call the gui’s
show
method
- Parameters
coordinator (
coordinator.CoordinatorBase
) – Coordinator used to communicate between GUI and controllerset_defaults (bool) – whether default control parameters should be set on startup – only to be used for development or testing
screenshot (bool) – whether alarms should be raised to take a screenshot, should never be used on a live system.
- Returns
The
PySide2.QtWidgets.QApplication
andPVP_Gui
- Return type
(tuple)
GUI Widgets¶
Control Panel
Control panel that starts/stops ventilation, controls system and GUI parameters
Alarm Bar
Bar that displays and alarms and allows them to be muted or dismissed
Display
Widgets to allow user control of ventilation parameters, displays sensor values and alarm limits.
Plot
Plot sensor waveforms
Components
Lower-level widgets that build the main widget classes
Dialog
Open dialogue windows to display messages to the user and ask for confirmation
Control Panel¶
The Control Panel starts and stops ventilation and controls runtime options
Classes:
|
Representation of alarm status and parameters |
|
An enumeration. |
|
The control panel starts and stops ventilation and controls runtime settings |
|
Track state of connection with Controller |
|
Button to lock and unlock controls |
|
Simple extension of toggle button with styling for clearer ‘ON’ vs ‘OFF’ |
|
with respct to https://stackoverflow.com/a/51057516 |
|
Button to start and stop Ventilation, created by |
|
Simple widget to display ventilation time! |
|
alias of |
Functions:
|
Retreive the currently running instance of the GUI |
|
module function to return a |
-
class
pvp.gui.widgets.control_panel.
Control_Panel
[source]¶ The control panel starts and stops ventilation and controls runtime settings
It creates:
Start/stop button
- Status indicator - a clock that increments with heartbeats,
or some other visual indicator that things are alright
Version indicator
Buttons to select options like cycle autoset and automatic breath detection
Args:
Button to start and stop ventilation
- Type
Button used to lock controls
- Type
Methods:
_pressure_units_changed
(button)Emit the str of the current pressure units
init_ui
()Initialize all graphical elements and buttons!
Attributes:
cycle_autoset_changed
(*args, **kwargs)pressure_units_changed
(*args, **kwargs)staticMetaObject
-
pressure_units_changed
(*args, **kwargs) = <PySide2.QtCore.Signal object>¶ Signal emitted when pressure units have been changed.
Contains str of current pressure units
-
cycle_autoset_changed
(*args, **kwargs) = <PySide2.QtCore.Signal object>¶ Signal emitted when a different breath cycle control value is set to be autocalculated
-
_pressure_units_changed
(button)[source]¶ Emit the str of the current pressure units
- Parameters
button (
PySide2.QtWidgets.QPushButton
) – Button that was clicked
-
staticMetaObject
= <PySide2.QtCore.QMetaObject object>¶
-
class
pvp.gui.widgets.control_panel.
Start_Button
(*args, **kwargs)[source]¶ Button to start and stop Ventilation, created by
Control_Panel
Methods:
load_pixmaps
()Load pixmaps to
Start_Button.pixmaps
set_state
(state)Set state of button
Attributes:
states
staticMetaObject
-
states
= ['OFF', 'ON', 'ALARM']¶ Possible states of Start_Button
-
load_pixmaps
()[source]¶ Load pixmaps to
Start_Button.pixmaps
-
set_state
(state)[source]¶ Set state of button
Should only be called by other objects (as there are checks to whether it’s ok to start/stop that we shouldn’t be aware of)
- Parameters
state (str) – one of
('OFF', 'ON', 'ALARM')
-
staticMetaObject
= <PySide2.QtCore.QMetaObject object>¶
-
-
class
pvp.gui.widgets.control_panel.
Lock_Button
(*args, **kwargs)[source]¶ Button to lock and unlock controls
Created by
Control_Panel
Methods:
load_pixmaps
()Load pixmaps used to display lock state to
Lock_Button.pixmaps
set_state
(state)Set lock state of button
Attributes:
states
staticMetaObject
-
states
= ['DISABLED', 'UNLOCKED', 'LOCKED']¶ Possible states of Lock Button
-
load_pixmaps
()[source]¶ Load pixmaps used to display lock state to
Lock_Button.pixmaps
-
set_state
(state)[source]¶ Set lock state of button
Should only be called by other objects (as there are checks to whether it’s ok to start/stop that we shouldn’t be aware of)
- Parameters
state (str) –
('OFF', 'ON', 'ALARM')
-
staticMetaObject
= <PySide2.QtCore.QMetaObject object>¶
-
-
class
pvp.gui.widgets.control_panel.
HeartBeat
(update_interval: int = 100, timeout_dur: int = 5000)[source]¶ Track state of connection with Controller
Check when we last had contact with controller every
HeartBeat.update_interval
ms, if longer thanHeartBeat.timeout_dur
then emit a timeout signal- Parameters
-
timer
¶ Timer that checks for last contact
- Type
PySide2.QtCore.QTimer
Methods:
_heartbeat
()Called every (update_interval) milliseconds to set the check the status of the heartbeat.
beatheart
(heartbeat_time)Slot that receives timestamps of last contact with controller
init_ui
()Initialize labels and status indicator
set_indicator
([state])Set visual indicator
set_state
(state)Set running state
start_timer
([update_interval])Start
HeartBeat.timer
to check for contact with controllerstop_timer
()Stop timer and clear text
Attributes:
heartbeat
(*args, **kwargs)staticMetaObject
timeout
(*args, **kwargs)-
timeout
(*args, **kwargs) = <PySide2.QtCore.Signal object>¶ Signal that a timeout has occurred – too long between contact with controller.
-
heartbeat
(*args, **kwargs) = <PySide2.QtCore.Signal object>¶ Signal that requests to affirm contact with controller if no message has been received in timeout duration
-
set_state
(state)[source]¶ Set running state
if just starting reset
HeartBeat._last_heartbeat
- Parameters
state (bool) – Whether we are starting (True) or stopping (False)
-
set_indicator
(state=None)[source]¶ Set visual indicator
- Parameters
state ('ALARM', 'OFF', 'NORMAL') – Current state of connection with controller
-
start_timer
(update_interval=None)[source]¶ Start
HeartBeat.timer
to check for contact with controller- Parameters
update_interval (int) – How often (in ms) the timer should be updated. if None, use
self.update_interval
-
beatheart
(heartbeat_time)[source]¶ Slot that receives timestamps of last contact with controller
- Parameters
heartbeat_time (float) – timestamp of last contact with controller
-
_heartbeat
()[source]¶ Called every (update_interval) milliseconds to set the check the status of the heartbeat.
-
staticMetaObject
= <PySide2.QtCore.QMetaObject object>¶
-
class
pvp.gui.widgets.control_panel.
StopWatch
(update_interval: float = 100, *args, **kwargs)[source]¶ Simple widget to display ventilation time!
- Parameters
update_interval (float) – update clock every n seconds
*args – passed to
PySide2.QtWidgets.QLabel
**kwargs – passed to
PySide2.QtWidgets.QLabel
Methods:
__init__
([update_interval])Simple widget to display ventilation time!
_update_time
()init_ui
()start_timer
([update_interval])- param update_interval
How often (in ms) the timer should be updated.
stop_timer
()Stop timer and reset label
Attributes:
staticMetaObject
-
__init__
(update_interval: float = 100, *args, **kwargs)[source]¶ Simple widget to display ventilation time!
- Parameters
update_interval (float) – update clock every n seconds
*args – passed to
PySide2.QtWidgets.QLabel
**kwargs – passed to
PySide2.QtWidgets.QLabel
-
staticMetaObject
= <PySide2.QtCore.QMetaObject object>¶
Alarm Bar¶
The Alarm_Bar
displays Alarm
status with Alarm_Card
widgets
and plays alarm sounds with the Alarm_Sound_Player
Classes:
|
Representation of alarm status and parameters |
|
An enumeration. |
|
An enumeration. |
Holds and manages a collection of |
|
|
Representation of an alarm raised by |
|
The Alarm Manager |
|
Plays alarm sounds to reflect current alarm severity and active duration with |
Functions:
|
Initialize a logger for logging events. |
|
module function to return a |
-
class
pvp.gui.widgets.alarm_bar.
Alarm_Bar
[source]¶ Holds and manages a collection of
Alarm_Card
s and communicates requests for dismissal to theAlarm_Manager
The alarm bar also manages the
Alarm_Sound_Player
-
alarms
¶ A list of active alarms
- Type
-
alarm_cards
¶ A list of active alarm cards
- Type
-
sound_player
¶ Class that plays alarm sounds!
- Type
Methods:
add_alarm
(alarm)Add an alarm created by the
Alarm_Manager
to the bar.clear_alarm
([alarm, alarm_type])Remove an alarm card, update appearance and sound player to reflect current max severity
init_ui
()Initialize the UI
Create pixmaps from standard icons to display for different alarm types
set_icon
([state])Change the icon and bar appearance to reflect the alarm severity
Call
set_icon()
with highest severity inAlarm_Bar.alarms
Attributes:
Current maximum
AlarmSeverity
-
make_icons
()[source]¶ Create pixmaps from standard icons to display for different alarm types
Store in
Alarm_Bar.icons
-
add_alarm
(alarm: pvp.alarm.alarm.Alarm)[source]¶ Add an alarm created by the
Alarm_Manager
to the bar.If an alarm alreaady exists with that same
AlarmType
,Alarm_Bar.clear_alarm()
Insert new alarm in order the prioritizes alarm severity with highest severity on right
Set alarm sound and begin playing if not already.
- Parameters
alarm (
Alarm
) – Alarm to be added
-
clear_alarm
(alarm: pvp.alarm.alarm.Alarm = None, alarm_type: pvp.alarm.AlarmType = None)[source]¶ Remove an alarm card, update appearance and sound player to reflect current max severity
Must pass one of either
alarm
oralarm_type
-
update_icon
()[source]¶ Call
set_icon()
with highest severity inAlarm_Bar.alarms
-
set_icon
(state: pvp.alarm.AlarmSeverity = None)[source]¶ Change the icon and bar appearance to reflect the alarm severity
- Parameters
state (
AlarmSeverity
) – Alarm Severity to display, if None change to default display
-
property
alarm_level
¶ Current maximum
AlarmSeverity
- Returns
-
staticMetaObject
= <PySide2.QtCore.QMetaObject object>¶
-
-
class
pvp.gui.widgets.alarm_bar.
Alarm_Card
(alarm: pvp.alarm.alarm.Alarm)[source]¶ Representation of an alarm raised by
Alarm_Manager
in GUI.If allowed by alarm (by
latch
setting) , allows user to dismiss/silence alarm.Otherwise request to dismiss is logged by
Alarm_Manager
and the card is dismissed when the conditions that generated the alarm are no longer met.- Parameters
alarm (
Alarm
) – Alarm to represent
-
severity
¶ The severity of the represented alarm
- Type
Button that requests an alarm be dismissed
- Type
PySide2.QtWidgets.QPushButton
Methods:
_dismiss
()Gets the
Alarm_Manager
instance and callsAlarm_Manager.dismiss_alarm()
init_ui
()Initialize graphical elements
Attributes:
-
init_ui
()[source]¶ Initialize graphical elements
Create labels
Set stylesheets
Create and connect dismiss button
Returns:
-
_dismiss
()[source]¶ Gets the
Alarm_Manager
instance and callsAlarm_Manager.dismiss_alarm()
Also change appearance of close button to reflect requested dismissal
-
staticMetaObject
= <PySide2.QtCore.QMetaObject object>¶
-
class
pvp.gui.widgets.alarm_bar.
Alarm_Sound_Player
(increment_delay: int = 10000, *args, **kwargs)[source]¶ Plays alarm sounds to reflect current alarm severity and active duration with
PySide2.QtMultimedia.QSoundEffect
objectsAlarm sounds indicate severity with the number and pitch of tones in a repeating tone cluster (eg. low severity sounds have a single repeating tone, but high-severity alarms have three repeating tones)
They indicate active duration by incrementally removing a low-pass filter and making tones have a sharper attack and decay.
When an alarm of any severity is started the
<severity_0.wav
file begins playing, and a timer is started to callAlarm_Sound_Player.increment_level()
- Parameters
increment_delay (int) – Delay between calling
Alarm_Sound_Player.increment_level()
**kwargs (*args,) –
passed to
PySide2.QtWidgets.QWidget
-
idx
¶ Dictionary of dictionaries allowing sounds to be accessed like
self.idx[AlarmSeverity][level]
- Type
-
_increment_timer
¶ Timer that increments alarm sound level
- Type
PySide2.QtCore.QTimer
-
_changing_track
¶ used to ensure single sound changing call happens at a time.
- Type
Methods:
If current level is below the maximum level, increment with
Alarm_Sound_Player.set_sound()
Returns:Load audio files in
pvp/external/audio
and add toAlarm_Sound_Player.idx
play
()Start sound playback
set_mute
(mute)Set mute state
set_sound
([severity, level])Set sound to be played
stop
()Stop sound playback
Attributes:
mapping between string representations of severities used by filenames and
AlarmSeverity
-
severity_map
= {'high': <AlarmSeverity.HIGH: 3>, 'low': <AlarmSeverity.LOW: 1>, 'medium': <AlarmSeverity.MEDIUM: 2>}¶ mapping between string representations of severities used by filenames and
AlarmSeverity
-
init_audio
()[source]¶ Load audio files in
pvp/external/audio
and add toAlarm_Sound_Player.idx
-
play
()[source]¶ Start sound playback
Play sound listed as
Alarm_Sound_Player._current_sound
Note
Alarm_Sound_Player.set_sound()
must be called first.
-
set_sound
(severity: pvp.alarm.AlarmSeverity = None, level: int = None)[source]¶ Set sound to be played
At least an
AlarmSeverity
must be provided.- Parameters
severity (
AlarmSeverity
) – Severity of alarm sound to playlevel (int) – level (corresponding to active duration) of sound to play
-
increment_level
()[source]¶ If current level is below the maximum level, increment with
Alarm_Sound_Player.set_sound()
Returns:
-
staticMetaObject
= <PySide2.QtCore.QMetaObject object>¶
Display¶
Unified monitor & control widget
Displays sensor values, and can optionally control system settings.
The PVP_Gui
instantiates display widgets according to the contents of values.DISPLAY_CONTROL
and values.DISPLAY_MONITOR
Classes:
|
An enumeration. |
|
Message containing ventilation control parameters. |
|
Unified widget for display of sensor values and control of ventilation parameters |
|
Slider capable of representing floats |
|
Editable label https://gist.github.com/mfessenden/baa2b87b8addb0b60e54a11c1da48046 |
|
Widget to display current value in a bar graph along with alarm limits |
|
|
|
Class to parameterize how a value is used in PVP. |
|
Canonical names of all values used in PVP. |
Functions:
|
Initialize a logger for logging events. |
|
module function to return a |
|
Creates a dialog box to display a message. |
-
class
pvp.gui.widgets.display.
Display
(value: pvp.common.values.Value, update_period: float = 0.5, enum_name: pvp.common.values.ValueName = None, button_orientation: str = 'left', control_type: Union[None, str] = None, style: str = 'dark', *args, **kwargs)[source]¶ Unified widget for display of sensor values and control of ventilation parameters
Displayed values are updated according to
Dispaly.timed_update()
- Parameters
value (
Value
) – Value object to representupdate_period (float) – Amount of time between updates of the textual display of values
enum_name (
ValueName
) – Value name of object to representbutton_orientation ('left', 'right') – whether the controls are drawn on the
'left'
or'right'
control_type (None, 'slider', 'record') – type of control - either
None
(no control),slider
(a slider can be opened to set a value), orrecord
where recent sensor values are averaged and used to set the control value. Both types of control allow values to be input from the keyboard by clicking on the editable labelstyle ('light', 'dark') – whether the widget is
'dark'
(light text on dark background) or'light'
(dark text on light background**kwargs (*args,) –
passed on to
PySide2.QtWidgets.QWidget
-
self.
name
¶ Unpacked from
value
-
self.
units
¶ Unpacked from
value
-
self.
abs_range
¶ Unpacked from
value
-
self.
safe_range
¶ Unpacked from
value
-
self.
alarm_range
¶ initialized from
value
, but updated by alarm manager
-
self.
decimals
¶ Unpacked from
value
-
self.
orientation
¶ whether the controls are drawn on the
'left'
or'right'
- Type
‘left’, ‘right’
-
self.
control
¶ type of control - either
None
(no control),slider
(a slider can be opened to set a value), orrecord
where recent sensor values are averaged and used to set the control value.- Type
None, ‘slider’, ‘record’
-
self.
_style
¶ whether the widget is
'dark'
(light text on dark background) or'light'
(dark text on light background)- Type
‘light’, ‘dark’
Methods:
_value_changed
(new_value)“outward-directed” method to emit new changed control value when changed by this widget
init_ui
()UI is initialized in several stages
Basically two methods.
Create widgets to display sensed value alongside set value
redraw
()Redraw all graphical elements to ensure internal model matches view
set_locked
(locked)Set locked status of control
set_units
(units)Set pressure units to display as cmH2O or hPa.
Refresh textual sensor values only periodically to prevent them from being totally unreadable from being changed too fast.
toggle_control
(state)Toggle the appearance of the slider, if a slider
toggle_record
(state)Toggle the recording state, if a recording control
update_limits
(control)Update the alarm range and the GUI elements corresponding to it
update_sensor_value
(new_value)Receive new sensor value and update display widgets
update_set_value
(new_value)Update to reflect new control value set from elsewhere (inwardly directed setter)
Attributes:
Current visual display of alarm severity
Check if value has been set for this control.
value_changed
(*args, **kwargs)Signal emitted when controlled value of display object has changed.
-
value_changed
(*args, **kwargs) = <PySide2.QtCore.Signal object>¶ Signal emitted when controlled value of display object has changed.
Contains new value (float)
-
init_ui
()[source]¶ UI is initialized in several stages
0: this method, get stylesheets based on
self._style
and call remaining initialization methods1:
Display.init_ui_labels()
- create generic labels shared by all display objects2:
Display.init_ui_toggle_button()
- create the toggle or record button used by controls3:
Display.init_ui_limits()
- create a plot that displays the sensor value graphically relative to the alarm limits4:
Display.init_ui_slider()
orDisplay.ini_ui_record()
- depending on what type of control this is5:
Display.init_ui_layout()
since the results of the previous steps varies, do all layout at the end depending on orientation6:
Display.init_ui_signals()
connect slots and signals
-
init_ui_layout
()[source]¶ Basically two methods… lay objects out depending on whether we’re oriented with our button to the left or right
-
toggle_control
(state)[source]¶ Toggle the appearance of the slider, if a slider
- Parameters
state (bool) – Whether to show or hide the slider
-
toggle_record
(state)[source]¶ Toggle the recording state, if a recording control
- Parameters
state (bool) – Whether recording should be started or stopped. when started, start storing new sensor values in a list. when stopped, average thgem and emit new value.
-
_value_changed
(new_value: float)[source]¶ “outward-directed” method to emit new changed control value when changed by this widget
Pop a confirmation dialog if values are set outside the safe range.
-
update_set_value
(new_value: float)[source]¶ Update to reflect new control value set from elsewhere (inwardly directed setter)
- Parameters
new_value (float) – new value to set!
-
update_sensor_value
(new_value: float)[source]¶ Receive new sensor value and update display widgets
- Parameters
new_value (float) – new sensor value!
-
update_limits
(control: pvp.common.message.ControlSetting)[source]¶ Update the alarm range and the GUI elements corresponding to it
- Parameters
control (
ControlSetting
) – control setting with min_value or max_value
-
redraw
()[source]¶ Redraw all graphical elements to ensure internal model matches view
Typically used when changing units
-
timed_update
()[source]¶ Refresh textual sensor values only periodically to prevent them from being totally unreadable from being changed too fast.
-
set_units
(units: str)[source]¶ Set pressure units to display as cmH2O or hPa.
Uses functions from
pvp.common.unit_conversion
such thatself._convert_in
converts internal, canonical units to displayed units (eg.cmH2O
is used by all other modules, so we convert it tohPa
self._convert_out
converts displayed units to send to other parts of the system
Note
currently unit conversion is only supported for Pressure.
- Parameters
units ('cmH2O', 'hPa') – new units to display
-
set_locked
(locked: bool)[source]¶ Set locked status of control
- Parameters
locked (bool) – If True, disable all controlling widgets, if False, re-enable.
-
property
is_set
¶ Check if value has been set for this control.
Used to check if all settings have been set preflight by
PVP_Gui
- Returns
whether we have an
Display.set_value
- Return type
-
property
alarm_state
¶ Current visual display of alarm severity
Change sensor value color to reflect the alarm state of that set parameter –
eg. if we have a HAPA alarm, set the PIP control to display as red.
- Returns
-
staticMetaObject
= <PySide2.QtCore.QMetaObject object>¶
-
class
pvp.gui.widgets.display.
Limits_Plot
(style: str = 'light', *args, **kwargs)[source]¶ Widget to display current value in a bar graph along with alarm limits
- Parameters
style ('light', 'dark') – Whether we are being displayed in a light or dark styled
Display
widget
-
set_value
¶ Set value of control, displayed as horizontal black line always set at center of bar
- Type
When initializing PlotWidget, parent and background are passed to
GraphicsWidget.__init__()
and all others are passed toPlotItem.__init__()
.Methods:
init_ui
()Create bar chart and horizontal lines to reflect
update_value
([min, max, sensor_value, set_value])Move the lines! Pass any of the represented values.
Set yrange to ensure that the set value is always in the center of the plot and that all the values are in range.
Attributes:
-
staticMetaObject
= <PySide2.QtCore.QMetaObject object>¶
-
init_ui
()[source]¶ Create bar chart and horizontal lines to reflect
Sensor Value
Set Value
High alarm limit
Low alarm limit
Plot¶
Widgets to plot waveforms of sensor values
The PVP_Gui
creates a Plot_Container
that allows the user to select
which plots (of those in
values.PLOT
) are displayedthe timescale (x range) of the displayed waveforms
Plots display alarm limits as red horizontal bars
Classes:
|
An enumeration. |
|
Message containing ventilation control parameters. |
|
Waveform plot of single sensor value. |
|
Container for multiple :class;`.Plot` objects |
|
Structured class for communicating sensor readings throughout PVP. |
|
Class to parameterize how a value is used in PVP. |
|
Canonical names of all values used in PVP. |
|
deque([iterable[, maxlen]]) –> deque object |
Data:
Update frequency of |
|
A |
Functions:
|
Retreive the currently running instance of the GUI |
|
Initialize a logger for logging events. |
|
module function to return a |
-
class
pvp.gui.widgets.plot.
Plot
(name, buffer_size=4092, plot_duration=10, plot_limits: tuple = None, color=None, units='', **kwargs)[source]¶ Waveform plot of single sensor value.
Plots values continuously, wrapping at zero rather than shifting x axis.
- Parameters
-
timestamps
¶ deque of timestamps
- Type
-
history
¶ deque of sensor values
- Type
-
cycles
¶ deque of breath cycles
- Type
When initializing PlotWidget, parent and background are passed to
GraphicsWidget.__init__()
and all others are passed toPlotItem.__init__()
.Attributes:
limits_changed
(*args, **kwargs)Methods:
Reset start time – return the scrolling time bar to position 0
set_duration
(dur)Set duration, or span of x axis.
set_safe_limits
(limits)Set the position of the max and min lines for a given value
set_units
(units)Set displayed units
update_value
(new_value)Update with new sensor value
-
limits_changed
(*args, **kwargs) = <PySide2.QtCore.Signal object>¶
-
set_duration
(dur: float)[source]¶ Set duration, or span of x axis.
- Parameters
dur (float) – span of x axis (in seconds)
-
update_value
(new_value: tuple)[source]¶ Update with new sensor value
- Parameters
new_value (tuple) – (timestamp from time.time(), breath_cycle, value)
-
set_safe_limits
(limits: pvp.common.message.ControlSetting)[source]¶ Set the position of the max and min lines for a given value
- Parameters
limits (
ControlSetting
) – Controlsetting that has either amin_value
ormax_value
defined
-
set_units
(units)[source]¶ Set displayed units
Currently only implemented for Pressure, display either in cmH2O or hPa
- Parameters
units ('cmH2O', 'hPa') – unit to display pressure as
-
staticMetaObject
= <PySide2.QtCore.QMetaObject object>¶
-
class
pvp.gui.widgets.plot.
Plot_Container
(plot_descriptors: Dict[pvp.common.values.ValueName, pvp.common.values.Value], *args, **kwargs)[source]¶ Container for multiple :class;`.Plot` objects
Allows user to show/hide different plots and adjust x-span (time zoom)
Note
Currently, the only unfortunately hardcoded parameter in the whole GUI is the instruction to initially hide FIO2, there should be an additional parameter in
Value
that says whether a plot should initialize as hidden or notTodo
Currently, colors are set to alternate between orange and light blue on initialization, but they don’t update when plots are shown/hidden, so the alternating can be lost and colors can look random depending on what’s selcted.
- Parameters
plot_descriptors (typing.Dict[ValueName, Value]) – dict of
Value
items to plot
-
slider
¶ slider used to set x span
- Type
PySide2.QtWidgets.QSlider
Methods:
init_ui
()Call
Plot.reset_start_time()
on all plotsset_duration
(duration)Set the current duration (span of the x axis) of all plots
set_safe_limits
(control)Try to set horizontal alarm limits on all relevant plots
toggle_plot
(state)Toggle the visibility of a plot.
update_value
(vals)Try to update all plots who have new sensorvalues
Attributes:
-
update_value
(vals: pvp.common.message.SensorValues)[source]¶ Try to update all plots who have new sensorvalues
- Parameters
vals (
SensorValues
) – Sensor Values to update plots with
-
toggle_plot
(state: bool)[source]¶ Toggle the visibility of a plot.
get the name of the plot from the
sender
’sobjectName
- Parameters
state (bool) – Whether the plot should be visible (True) or not (False)
-
set_safe_limits
(control: pvp.common.message.ControlSetting)[source]¶ Try to set horizontal alarm limits on all relevant plots
- Parameters
control (
ControlSetting
) – with eithermin_value
ormax_value
set
Returns:
-
set_duration
(duration: float)[source]¶ Set the current duration (span of the x axis) of all plots
Also make sure that the text box and slider reflect this duration
- Parameters
duration (float) – new duration to set
Returns:
-
reset_start_time
()[source]¶ Call
Plot.reset_start_time()
on all plots
-
staticMetaObject
= <PySide2.QtCore.QMetaObject object>¶
Components¶
Very basic components used by other widgets.
These are relatively sparsely documented because their operation is mostly self-explanatory
Classes:
|
Slider capable of representing floats |
|
Editable label https://gist.github.com/mfessenden/baa2b87b8addb0b60e54a11c1da48046 |
Custom key press handler https://gist.github.com/mfessenden/baa2b87b8addb0b60e54a11c1da48046 |
|
|
Simple extension of toggle button with styling for clearer ‘ON’ vs ‘OFF’ |
|
with respct to https://stackoverflow.com/a/51057516 |
|
Functions:
|
module function to return a |
-
class
pvp.gui.widgets.components.
DoubleSlider
(decimals=1, *args, **kargs)[source]¶ Slider capable of representing floats
Ripped off from and https://stackoverflow.com/a/50300848 ,
Thank you!!!
Methods:
_maximum
()_minimum
()maximum
(self)minimum
(self)setDecimals
(decimals)setMaximum
(self, arg__1)setMinimum
(self, arg__1)setSingleStep
(self, arg__1)setValue
(self, arg__1)singleStep
(self)value
(self)Attributes:
doubleValueChanged
(*args, **kwargs)-
doubleValueChanged
(*args, **kwargs) = <PySide2.QtCore.Signal object>¶
-
staticMetaObject
= <PySide2.QtCore.QMetaObject object>¶
-
-
class
pvp.gui.widgets.components.
KeyPressHandler
[source]¶ Custom key press handler https://gist.github.com/mfessenden/baa2b87b8addb0b60e54a11c1da48046
Attributes:
escapePressed
(*args, **kwargs)returnPressed
(*args, **kwargs)Methods:
eventFilter
(self, watched, event)-
escapePressed
(*args, **kwargs) = <PySide2.QtCore.Signal object>¶
-
returnPressed
(*args, **kwargs) = <PySide2.QtCore.Signal object>¶
-
staticMetaObject
= <PySide2.QtCore.QMetaObject object>¶
-
-
class
pvp.gui.widgets.components.
EditableLabel
(parent=None, **kwargs)[source]¶ Editable label https://gist.github.com/mfessenden/baa2b87b8addb0b60e54a11c1da48046
Methods:
Escape event handler
labelPressedEvent
(event)Set editable if the left mouse button is clicked
Indicates the widget text has been updated
Return/enter event handler
setEditable
(editable)Action to make the widget editable
setText
(text)Standard QLabel text setter
text
()Standard QLabel text getter
Attributes:
textChanged
(*args, **kwargs)-
textChanged
(*args, **kwargs) = <PySide2.QtCore.Signal object>¶
-
staticMetaObject
= <PySide2.QtCore.QMetaObject object>¶
-
-
class
pvp.gui.widgets.components.
QHLine
(parent=None, color='#FFFFFF')[source]¶ with respct to https://stackoverflow.com/a/51057516
Methods:
setColor
(color)Attributes:
-
staticMetaObject
= <PySide2.QtCore.QMetaObject object>¶
-
-
class
pvp.gui.widgets.components.
QVLine
(parent=None, color='#FFFFFF')[source]¶ Methods:
setColor
(color)Attributes:
-
staticMetaObject
= <PySide2.QtCore.QMetaObject object>¶
-
-
class
pvp.gui.widgets.components.
OnOffButton
(state_labels: Tuple[str, str] = 'ON', 'OFF', toggled: bool = False, *args, **kwargs)[source]¶ Simple extension of toggle button with styling for clearer ‘ON’ vs ‘OFF’
- Parameters
Methods:
__init__
([state_labels, toggled])- param state_labels
tuple of strings to set when toggled and untoggled
set_state
(state)Attributes:
-
__init__
(state_labels: Tuple[str, str] = 'ON', 'OFF', toggled: bool = False, *args, **kwargs)[source]¶
-
staticMetaObject
= <PySide2.QtCore.QMetaObject object>¶
Dialog¶
Function to display a dialog to the user and receive feedback!
Functions:
|
Retreive the currently running instance of the GUI |
|
Initialize a logger for logging events. |
|
Creates a dialog box to display a message. |
-
pvp.gui.widgets.dialog.
pop_dialog
(message: str, sub_message: str = None, modality: <class 'PySide2.QtCore.Qt.WindowModality'> = PySide2.QtCore.Qt.WindowModality.NonModal, buttons: <class 'PySide2.QtWidgets.QMessageBox.StandardButton'> = PySide2.QtWidgets.QMessageBox.StandardButton.Ok, default_button: <class 'PySide2.QtWidgets.QMessageBox.StandardButton'> = PySide2.QtWidgets.QMessageBox.StandardButton.Ok)[source]¶ Creates a dialog box to display a message.
Note
This function does not call .exec_ on the dialog so that it can be managed by the caller.
- Parameters
message (str) – Message to be displayed
sub_message (str) – Smaller message displayed below main message (InformativeText)
modality (QtCore.Qt.WindowModality) – Modality of dialog box - QtCore.Qt.NonModal (default) is unblocking, QtCore.Qt.WindowModal is blocking
buttons (QtWidgets.QMessageBox.StandardButton) – Buttons for the window, can be
|
ed togetherdefault_button (QtWidgets.QMessageBox.StandardButton) – one of
buttons
, the highlighted button
- Returns
QtWidgets.QMessageBox
GUI Stylesheets¶
Classes:
|
An enumeration. |
Data:
inter-update interval (seconds) for |
Functions:
|
Apply Dark Theme to the Qt application instance. |
-
pvp.gui.styles.
MONITOR_UPDATE_INTERVAL
= 0.5¶ inter-update interval (seconds) for
Monitor
- Type
(float)
-
pvp.gui.styles.
set_dark_palette
(app)[source]¶ Apply Dark Theme to the Qt application instance.
- borrowed from https://github.com/gmarull/qtmodern/blob/master/qtmodern/styles.py
- Args:
app (QApplication): QApplication instance.
The GUI is written using PySide2 and consists of one main PVP_Gui
object that instantiates a series of GUI Widgets. The GUI is responsible for setting ventilation control parameters
and sending them to the controller
(see set_control()
), as well as receiving and displaying sensor values (get_sensors()
).
The GUI also feeds the Alarm_Manager
SensorValues
objects so that it can compute alarm state. The Alarm_Manager
reciprocally updates the GUI with Alarm
s (PVP_Gui.handle_alarm()
) and Alarm limits (PVP_Gui.limits_updated()
).
The main polling loop of the GUI is PVP_Gui.update_gui()
which queries the controller for new SensorValues
and distributes
them to all listening widgets (see method documentation for more details). The rest of the GUI is event driven, usually with Qt
Signals and Slots.
The GUI is configured by the values
module, in particular it creates
Display
widgets in the left “sensor monitor” box from allValue
s inDISPLAY_MONITOR
,Display
widgets in the right “control” box from allValue
s inDISPLAY_CONTROL
, andPlot
widgets in the center plot box from allValue
s inPLOT
The GUI is not intended to be launched alone, as it needs an active coordinator
to communicate with the controller process
and a few prelaunch preparations (launch_gui()
). PVP should be started like:
python3 -m pvp.main
Module Overview¶
Screenshot¶

Controller¶
Screenshot¶

The Controller consists of one main controller
object that receives sensor-data, and computes control parameters, to change valve settings. The controller receives ventilation control parameters (see set_control()
), and can provide the currently active set of controls (see???)
The Controller also feeds the Logger
SensorValues
objects so that it can store high-temporal resolution data.
The main polling loop of the Controller is PVP_Gui.update_gui()
which queries the Hardware for new variables, that are wired up in a new SensorValues
and distributes
them to all listening widgets (see method documentation for more details).
The Controller is configured by the values
module, in particular it creates
Display
widgets in the left “sensor monitor” box from allValue
s inDISPLAY_MONITOR
,
The Controller can be launched alone:
but was not intended to be launched alone.
add logging
Classes:
|
Representation of alarm status and parameters |
|
An enumeration. |
|
An enumeration. |
|
Physics simulator for inflating a balloon with an attached PEEP valve. |
|
Abstract controller class for simulation/hardware. |
|
Uses ControlModuleBase to control the hardware. |
|
Controlling Simulation. |
|
Message containing ventilation control parameters. |
|
Class to save control values, analogous to SensorValues. |
|
Class for logging numerical respiration data and control settings. Creates a hdf5 file with this general structure: / root |— waveforms (group) | |— time | pressure_data | flow_out | control_signal_in | control_signal_out | FiO2 | Cycle No. | |— controls (group) | |— (time, controllsignal) | |— derived_quantities (group) | |— (time, Cycle No, I_PHASE_DURATION, PIP_TIME, PEEP_time, PIP, PIP_PLATEAU, PEEP, VTE ) | |. |
|
Class to save derived values, analogous to SensorValues. |
|
Structured class for communicating sensor readings throughout PVP. |
|
Canonical names of all values used in PVP. |
|
count(start=0, step=1) –> count object |
|
deque([iterable[, maxlen]]) –> deque object |
Data:
|
The central part of internal API. |
Functions:
|
Generates control module. |
|
Initialize a logger for logging events. |
|
Defines a decorator for a 50ms timeout. |
-
class
pvp.controller.control_module.
ControlModuleBase
(save_logs: bool = False, flush_every: int = 10)[source]¶ Bases:
object
Abstract controller class for simulation/hardware.
1. General notes: All internal variables fall in three classes, denoted by the beginning of the variable:
COPY_varname: These are copies (for safe threading purposes) that are regularly sync’ed with internal variables.
__varname: These are variables only used in the ControlModuleBase-Class
_varname: These are variables used in derived classes.
2. Set and get values. Internal variables should only to be accessed though the set_ and get_ functions.
These functions act on COPIES of internal variables (__ and _), that are sync’d every few iterations. How often this is done is adjusted by the variable self._NUMBER_CONTROLL_LOOPS_UNTIL_UPDATE. To avoid multiple threads manipulating the same variables at the same time, every manipulation of COPY_ is surrounded by a thread lock.
- Public Methods:
get_sensors(): Returns a copy of the current sensor values.
get_alarms(): Returns a List of all alarms, active and logged
get_control(ControlSetting): Sets a controll-setting. Is updated at latest within self._NUMBER_CONTROLL_LOOPS_UNTIL_UPDATE
get_past_waveforms(): Returns a List of waveforms of pressure and volume during at the last N breath cycles, N<self. _RINGBUFFER_SIZE, AND clears this archive.
start(): Starts the main-loop of the controller
stop(): Stops the main-loop of the controller
set_control(): Set the control
interrupt(): Interrupt the controller, and re-spawns the thread. Used to restart a stuck controller
is_running(): Returns a bool whether the main-thread is running
get_heartbeat(): Returns a heartbeat, more specifically, the continuously increasing iteration-number of the main control loop.
Initializes the ControlModuleBase class.
- Parameters
save_logs (bool, optional) – Should sensor data and controls should be saved with the
DataLogger
? Defaults to False.flush_every (int, optional) – Flush and rotate logs every n breath cycles. Defaults to 10.
- Raises
alert – [description]
Methods:
_PID_update
(dt)This instantiates the PID control algorithms. During the breathing cycle, it goes through the four states: 1) Rise to PIP, speed is controlled by flow (variable: __SET_PIP_GAIN) 2) Sustain PIP pressure 3) Quick fall to PEEP 4) Sustaint PEEP pressure Once the cycle is complete, it checks the cycle for any alarms, and starts a new one. A record of pressure/volume waveforms is kept and saved.
__init__
([save_logs, flush_every])Initializes the ControlModuleBase class.
Resets the internal controller cycle to zero, i.e. restarts the breath cycle.
Produces the INSPIRATORY control-signal that has been calculated in __calculate_control_signal_in(dt)
Produces the EXPIRATORY control-signal for the different states, i.e. open/close.
Makes a copy of internal variables.
Prototype method to start main PID loop.
A method callable from the outside to get a copy of the alarms, that the controller checks: High airway pressure, and technical alarms.
get_control
(control_setting_name)A method callable from the outside to get current control settings.
Returns an independent heart-beat of the controller, i.e. the internal loop counter incremented in _start_mainloop.
Public method to return a list of past waveforms from __cycle_waveform_archive. Note: After calling this function, archive is emptied! The format is - Returns a list of [Nx3] waveforms, of [time, pressure, volume] - Most recent entry is waveform_list[-1].
A method callable from the outside to get a copy of sensorValues
If the controller seems stuck, this generates a new thread, and starts the main loop.
Public Method to assess whether the main loop thread is running.
set_breath_detection
(breath_detection)set_control
(control_setting)A method callable from the outside to set alarms.
start
()Method to start _start_mainloop as a thread.
stop
()Method to stop the main loop thread, and close the logfile.
-
__init__
(save_logs: bool = False, flush_every: int = 10)[source]¶ Initializes the ControlModuleBase class.
- Parameters
save_logs (bool, optional) – Should sensor data and controls should be saved with the
DataLogger
? Defaults to False.flush_every (int, optional) – Flush and rotate logs every n breath cycles. Defaults to 10.
- Raises
alert – [description]
-
_initialize_set_to_COPY
()[source]¶ Makes a copy of internal variables. This is used to facilitate threading
-
__analyze_last_waveform
()¶ - This goes through the last waveform, and updates the internal variables:
VTE, PEEP, PIP, PIP_TIME, I_PHASE, FIRST_PEEP and BPM.
-
get_sensors
() → pvp.common.message.SensorValues[source]¶ A method callable from the outside to get a copy of sensorValues
- Returns
A set of current sensorvalues, handeled by the controller.
- Return type
-
get_alarms
() → Union[None, Tuple[pvp.alarm.alarm.Alarm]][source]¶ A method callable from the outside to get a copy of the alarms, that the controller checks: High airway pressure, and technical alarms.
-
set_control
(control_setting: pvp.common.message.ControlSetting)[source]¶ A method callable from the outside to set alarms. This updates the entries of COPY with new control values.
- Parameters
control_setting (ControlSetting) – [description]
-
get_control
(control_setting_name: pvp.common.values.ValueName) → pvp.common.message.ControlSetting[source]¶ A method callable from the outside to get current control settings. This returns values of COPY to the outside world.
- Parameters
control_setting_name (ValueName) – The specific control asked for
- Returns
ControlSettings-Object that contains relevant data
- Return type
-
__get_PID_error
(ytarget, yis, dt, RC)¶ Calculates the three terms for PID control. Also takes a timestep “dt” on which the integral-term is smoothed.
-
__calculate_control_signal_in
(dt)¶ - Calculates the PID control signal by:
Combining the the three gain parameters.
And smoothing the control signal with a moving window of three frames (~10ms)
- Parameters
dt (float) – timestep
-
_get_control_signal_in
()[source]¶ Produces the INSPIRATORY control-signal that has been calculated in __calculate_control_signal_in(dt)
- Returns
the numerical control signal for the inspiratory prop valve
- Return type
-
_get_control_signal_out
()[source]¶ Produces the EXPIRATORY control-signal for the different states, i.e. open/close
- Returns
numerical control signal for expiratory side: open (1) close (0)
- Return type
-
_control_reset
()[source]¶ Resets the internal controller cycle to zero, i.e. restarts the breath cycle. Used for autonomous breath detection.
-
__test_for_alarms
()¶ - Implements tests that are to be executed in the main control loop:
Test for HAPA
Test for Technical Alert, making sure sensor values are plausible
Test for Technical Alert, make sure continuous in contact
Currently: Alarms are time.time() of first occurance.
-
__start_new_breathcycle
()¶ - Some housekeeping. This has to be executed when the next breath cycles starts:
starts new breathcycle
initializes newe __cycle_waveform
analyzes last breath waveform for PIP, PEEP etc. with __analyze_last_waveform()
flushes the logfile
-
_PID_update
(dt)[source]¶ This instantiates the PID control algorithms. During the breathing cycle, it goes through the four states:
Rise to PIP, speed is controlled by flow (variable: __SET_PIP_GAIN)
Sustain PIP pressure
Quick fall to PEEP
Sustaint PEEP pressure
Once the cycle is complete, it checks the cycle for any alarms, and starts a new one. A record of pressure/volume waveforms is kept and saved
- Parameters
dt (float) – timesstep since last update
-
__save_values
()¶ Helper function to reorganize key parameters in the main PID control loop, into a SensorValues object, that can be stored in the logfile, using a method from the DataLogger.
-
get_past_waveforms
()[source]¶ Public method to return a list of past waveforms from __cycle_waveform_archive. Note: After calling this function, archive is emptied! The format is
Returns a list of [Nx3] waveforms, of [time, pressure, volume]
Most recent entry is waveform_list[-1]
- Returns
[Nx3] waveforms, of [time, pressure, volume]
- Return type
-
_start_mainloop
()[source]¶ Prototype method to start main PID loop. Will depend on simulation or device, specified below.
-
interrupt
()[source]¶ If the controller seems stuck, this generates a new thread, and starts the main loop. No parameters have changed.
-
class
pvp.controller.control_module.
ControlModuleDevice
(save_logs=True, flush_every=10, config_file=None)[source]¶ Bases:
pvp.controller.control_module.ControlModuleBase
Uses ControlModuleBase to control the hardware.
Initializes the ControlModule for the physical system. Inherits methods from ControlModuleBase
- Parameters
save_logs (bool, optional) – Should logs be kept? Defaults to True.
flush_every (int, optional) – How often are log-files to be flushed, in units of main-loop-itertions? Defaults to 10.
config_file (str, optional) – Path to device config file, e.g. ‘pvp/io/config/dinky-devices.ini’. Defaults to None.
Methods:
__init__
([save_logs, flush_every, config_file])Initializes the ControlModule for the physical system.
_get_HAL
()Get sensor values from HAL, decorated with timeout.
Copies the current measurements to`COPY_sensor_values`, so that it can be queried from the outside.
_set_HAL
(valve_open_in, valve_open_out)Set Controls with HAL, decorated with a timeout.
This is the main loop.
This returns valves back to normal setting (in: closed, out: open)
-
__init__
(save_logs=True, flush_every=10, config_file=None)[source]¶ Initializes the ControlModule for the physical system. Inherits methods from ControlModuleBase
- Parameters
save_logs (bool, optional) – Should logs be kept? Defaults to True.
flush_every (int, optional) – How often are log-files to be flushed, in units of main-loop-itertions? Defaults to 10.
config_file (str, optional) – Path to device config file, e.g. ‘pvp/io/config/dinky-devices.ini’. Defaults to None.
-
_sensor_to_COPY
()[source]¶ Copies the current measurements to`COPY_sensor_values`, so that it can be queried from the outside.
-
_set_HAL
(valve_open_in, valve_open_out)[source]¶ Set Controls with HAL, decorated with a timeout.
As hardware communication is the speed bottleneck. this code is slightly optimized in so far as only changes are sent to hardware.
-
_get_HAL
()[source]¶ Get sensor values from HAL, decorated with timeout. As hardware communication is the speed bottleneck. this code is slightly optimized in so far as some sensors are queried only in certain phases of the breatch cycle. This is done to run the primary PID loop as fast as possible:
pressure is always queried
Flow is queried only outside of inspiration
In addition, oxygen is only read every 5 seconds.
-
class
pvp.controller.control_module.
Balloon_Simulator
(peep_valve)[source]¶ Bases:
object
Physics simulator for inflating a balloon with an attached PEEP valve. For math, see https://en.wikipedia.org/wiki/Two-balloon_experiment
Methods:
OUupdate
(variable, dt, mu, sigma, tau)This is a simple function to produce an OU process on variable.
_reset
()Resets Balloon to default settings.
set_flow_in
(Qin, dt)set_flow_out
(Qout, dt)update
(dt)
-
class
pvp.controller.control_module.
ControlModuleSimulator
(simulator_dt=None, peep_valve_setting=5)[source]¶ Bases:
pvp.controller.control_module.ControlModuleBase
Controlling Simulation.
Initializes the ControlModuleBase with the simple simulation (for testing/dev).
- Parameters
Methods:
__init__
([simulator_dt, peep_valve_setting])Initializes the ControlModuleBase with the simple simulation (for testing/dev).
Make the sensor value object from current (simulated) measurements
This is the main loop.
-
__init__
(simulator_dt=None, peep_valve_setting=5)[source]¶ Initializes the ControlModuleBase with the simple simulation (for testing/dev).
-
__SimulatedPropValve
(x)¶ This simulates the action of a proportional valve. Flow-current-curve eye-balled from generic prop vale with logistic activation.
-
__SimulatedSolenoid
(x)¶ This simulates the action of a two-state Solenoid valve.
-
pvp.controller.control_module.
get_control_module
(sim_mode=False, simulator_dt=None)[source]¶ Generates control module.
Control into a breathing cycle was accomplished with ahybrid system of state and PID control. During inspiration,we actively control pressure using a PID cycle to set theinspiratory valve. Expiration was then instantiated by closingthe inpiratory, and opening the expiratory valve to passivelyrelease PIP pressure as fast as possible. After reaching PEEP,we opened the inspiratory valve slightly to sustain PEEPusing the aforementioned manually operated PEEP-valveand to sustain a gentle flow of air through the system
The Raspberry pi allowed for the primary control loop torun at speeds exceeding≈320Hz, using≈40%of themaximum bandwidth of the analog-to-digital converterreading the sensors
In addition to pressure control, our software continuouslymonitors for autonomous breaths, high airway pressure,and general system status.Autonomous breathing wasdetected by transient pressure drops below PEEP. A detectedbreath triggered a new breath cycle. High airway pressureis defined as exceeding a certain pressure for a certain time(as to not be triggered by a cough). This triggered an alarm,and an immediate release of air to drop pressure to PEEP.The Controller also assesses whether numerical values arereasonable, and changing over time. If this is not the case,it raises an technical alarm. All alarms are collected andmaintained by an intelligent alarm manager, that provides theUI with the alarms to display in order of their importance.In addition to the alarm system, the controller monitorsfor autonomous breath events during peep. We define suchevents by a drop below the PEEP baseline exceeding somefixed threshold. If an autonomous drop was detected, thenext breath cycle is initiated
common module¶
Values¶
Parameterization of variables and values used in PVP.
Value
objects define the existence and behavior of values, including
creating Display
and Plot
widgets in the GUI, and the contents of
SensorValues
and ControlSetting
s used between the GUI and controller.
Data:
Values to control but not monitor. |
|
Control values that should also have a widget created in the GUI |
|
Those sensor values that should also have a widget created in the GUI |
|
Values that can be plotted |
|
Sensor values |
|
Declaration of all values used by PVP |
Classes:
|
Generic enumeration. |
|
Class to parameterize how a value is used in PVP. |
|
Canonical names of all values used in PVP. |
|
Instances are replaced with an appropriate value in Enum class suites. |
|
alias of |
-
class
pvp.common.values.
ValueName
(value)[source]¶ Bases:
enum.Enum
Canonical names of all values used in PVP.
Attributes:
-
PIP
= 1¶
-
PIP_TIME
= 2¶
-
PEEP
= 3¶
-
PEEP_TIME
= 4¶
-
BREATHS_PER_MINUTE
= 5¶
-
INSPIRATION_TIME_SEC
= 6¶
-
IE_RATIO
= 7¶
-
FIO2
= 8¶
-
VTE
= 9¶
-
PRESSURE
= 10¶
-
FLOWOUT
= 11¶
-
-
class
pvp.common.values.
Value
(name: str, units: str, abs_range: tuple, safe_range: tuple, decimals: int, control: bool, sensor: bool, display: bool, plot: bool = False, plot_limits: Union[None, Tuple[pvp.common.values.ValueName]] = None, control_type: Union[None, str] = None, group: Union[None, dict] = None, default: (<class 'int'>, <class 'float'>) = None)[source]¶ Bases:
object
Class to parameterize how a value is used in PVP.
Sets whether a value is a sensor value, a control value, whether it should be plotted, and other details for the rest of the system to determine how to use it.
Values should only be declared in this file so that they are kept consistent with
ValueName
and to not leak stray values anywhere else in the program.- Parameters
name (str) – Human-readable name of the value
units (str) – Human-readable description of units
abs_range (tuple) – tuple of ints or floats setting the logical limit of the value, eg. a percent between 0 and 100, (0, 100)
safe_range (tuple) –
tuple of ints or floats setting the safe ranges of the value,
note:
this is not the same thing as the user-set alarm values, though the user-set alarm values are initialized as ``safe_range``.
decimals (int) – the number of decimals of precision used when displaying the value
control (bool) – Whether or not the value is used to control ventilation
sensor (bool) – Whether or not the value is a measured sensor value
display (bool) – whether the value should be created as a
gui.widgets.Display
widget.plot (bool) – whether or not the value is plottable in the center plot window
plot_limits (None, tuple(ValueName)) – If plottable, and the plotted value has some alarm limits for another value, plot those limits as horizontal lines in the plot. eg. the PIP alarm range limits should be plotted on the Pressure plot
control_type (None, "slider", "record") – If a control sets whether the control should use a slider or be set by recording recent sensor values.
group (None, str) – Unused currently, but to be used to create subgroups of control & display widgets
default (None, int, float) – Default value, if any. (Not automatically set in the GUI.)
Methods:
__init__
(name, units, abs_range, safe_range, …)- param name
Human-readable name of the value
to_dict
()Gather up all attributes and return as a dict!
Attributes:
tuple of ints or floats setting the logical limit of the value, eg.
Whether or not the value is used to control ventilation
If a control sets whether the control should use a slider or be set by recording recent sensor values.
The number of decimals of precision used when displaying the value
Default value, if any.
Whether the value should be created as a
gui.widgets.Display
widget.Unused currently, but to be used to create subgroups of control & display widgets
Human readable name of value
whether or not the value is plottable in the center plot window
If plottable, and the plotted value has some alarm limits for another value, plot those limits as horizontal lines in the plot.
tuple of ints or floats setting the safe ranges of the value,
Whether or not the value is a measured sensor value
-
__init__
(name: str, units: str, abs_range: tuple, safe_range: tuple, decimals: int, control: bool, sensor: bool, display: bool, plot: bool = False, plot_limits: Union[None, Tuple[pvp.common.values.ValueName]] = None, control_type: Union[None, str] = None, group: Union[None, dict] = None, default: (<class 'int'>, <class 'float'>) = None)[source]¶ - Parameters
name (str) – Human-readable name of the value
units (str) – Human-readable description of units
abs_range (tuple) – tuple of ints or floats setting the logical limit of the value, eg. a percent between 0 and 100, (0, 100)
safe_range (tuple) –
tuple of ints or floats setting the safe ranges of the value,
note:
this is not the same thing as the user-set alarm values, though the user-set alarm values are initialized as ``safe_range``.
decimals (int) – the number of decimals of precision used when displaying the value
control (bool) – Whether or not the value is used to control ventilation
sensor (bool) – Whether or not the value is a measured sensor value
display (bool) – whether the value should be created as a
gui.widgets.Display
widget.plot (bool) – whether or not the value is plottable in the center plot window
plot_limits (None, tuple(ValueName)) – If plottable, and the plotted value has some alarm limits for another value, plot those limits as horizontal lines in the plot. eg. the PIP alarm range limits should be plotted on the Pressure plot
control_type (None, "slider", "record") – If a control sets whether the control should use a slider or be set by recording recent sensor values.
group (None, str) – Unused currently, but to be used to create subgroups of control & display widgets
default (None, int, float) – Default value, if any. (Not automatically set in the GUI.)
-
property
name
¶ Human readable name of value
- Returns
str
-
property
abs_range
¶ tuple of ints or floats setting the logical limit of the value, eg. a percent between 0 and 100, (0, 100)
- Returns
tuple
-
property
safe_range
¶ tuple of ints or floats setting the safe ranges of the value,
note:
this is not the same thing as the user-set alarm values, though the user-set alarm values are initialized as ``safe_range``.
- Returns
tuple
-
property
decimals
¶ The number of decimals of precision used when displaying the value
- Returns
int
-
property
default
¶ Default value, if any. (Not automatically set in the GUI.)
-
property
control
¶ Whether or not the value is used to control ventilation
- Returns
bool
-
property
sensor
¶ Whether or not the value is a measured sensor value
- Returns
bool
-
property
display
¶ Whether the value should be created as a
gui.widgets.Display
widget.- Returns
bool
-
property
control_type
¶ If a control sets whether the control should use a slider or be set by recording recent sensor values.
- Returns
None, “slider”, “record”
-
property
group
¶ Unused currently, but to be used to create subgroups of control & display widgets
- Returns
None, str
-
property
plot
¶ whether or not the value is plottable in the center plot window
- Returns
bool
-
property
plot_limits
¶ If plottable, and the plotted value has some alarm limits for another value, plot those limits as horizontal lines in the plot. eg. the PIP alarm range limits should be plotted on the Pressure plot
- Returns
None, typing.Tuple[ValueName]
-
pvp.common.values.
VALUES
= OrderedDict([(<ValueName.PIP: 1>, <pvp.common.values.Value object>), (<ValueName.PEEP: 3>, <pvp.common.values.Value object>), (<ValueName.BREATHS_PER_MINUTE: 5>, <pvp.common.values.Value object>), (<ValueName.INSPIRATION_TIME_SEC: 6>, <pvp.common.values.Value object>), (<ValueName.IE_RATIO: 7>, <pvp.common.values.Value object>), (<ValueName.PIP_TIME: 2>, <pvp.common.values.Value object>), (<ValueName.PEEP_TIME: 4>, <pvp.common.values.Value object>), (<ValueName.PRESSURE: 10>, <pvp.common.values.Value object>), (<ValueName.VTE: 9>, <pvp.common.values.Value object>), (<ValueName.FLOWOUT: 11>, <pvp.common.values.Value object>), (<ValueName.FIO2: 8>, <pvp.common.values.Value object>)])¶ Declaration of all values used by PVP
-
pvp.common.values.
SENSOR
= OrderedDict([(<ValueName.PIP: 1>, <pvp.common.values.Value object>), (<ValueName.PEEP: 3>, <pvp.common.values.Value object>), (<ValueName.BREATHS_PER_MINUTE: 5>, <pvp.common.values.Value object>), (<ValueName.INSPIRATION_TIME_SEC: 6>, <pvp.common.values.Value object>), (<ValueName.PRESSURE: 10>, <pvp.common.values.Value object>), (<ValueName.VTE: 9>, <pvp.common.values.Value object>), (<ValueName.FLOWOUT: 11>, <pvp.common.values.Value object>), (<ValueName.FIO2: 8>, <pvp.common.values.Value object>)])¶ Sensor values
Automatically generated as all
Value
objects inVALUES
wheresensor == True
-
pvp.common.values.
CONTROL
= OrderedDict([(<ValueName.PIP: 1>, <pvp.common.values.Value object>), (<ValueName.PEEP: 3>, <pvp.common.values.Value object>), (<ValueName.BREATHS_PER_MINUTE: 5>, <pvp.common.values.Value object>), (<ValueName.INSPIRATION_TIME_SEC: 6>, <pvp.common.values.Value object>), (<ValueName.IE_RATIO: 7>, <pvp.common.values.Value object>), (<ValueName.PIP_TIME: 2>, <pvp.common.values.Value object>), (<ValueName.PEEP_TIME: 4>, <pvp.common.values.Value object>)])¶ Values to control but not monitor.
Automatically generated as all
Value
objects inVALUES
wherecontrol == True
-
pvp.common.values.
DISPLAY_MONITOR
= OrderedDict([(<ValueName.PIP: 1>, <pvp.common.values.Value object>), (<ValueName.PEEP: 3>, <pvp.common.values.Value object>), (<ValueName.BREATHS_PER_MINUTE: 5>, <pvp.common.values.Value object>), (<ValueName.INSPIRATION_TIME_SEC: 6>, <pvp.common.values.Value object>), (<ValueName.PRESSURE: 10>, <pvp.common.values.Value object>), (<ValueName.VTE: 9>, <pvp.common.values.Value object>), (<ValueName.FLOWOUT: 11>, <pvp.common.values.Value object>), (<ValueName.FIO2: 8>, <pvp.common.values.Value object>)])¶ Those sensor values that should also have a widget created in the GUI
Automatically generated as all
Value
objects inVALUES
wheresensor == True
anddisplay == True
-
pvp.common.values.
DISPLAY_CONTROL
= OrderedDict([(<ValueName.PIP: 1>, <pvp.common.values.Value object>), (<ValueName.PEEP: 3>, <pvp.common.values.Value object>), (<ValueName.BREATHS_PER_MINUTE: 5>, <pvp.common.values.Value object>), (<ValueName.INSPIRATION_TIME_SEC: 6>, <pvp.common.values.Value object>), (<ValueName.IE_RATIO: 7>, <pvp.common.values.Value object>), (<ValueName.PIP_TIME: 2>, <pvp.common.values.Value object>)])¶ Control values that should also have a widget created in the GUI
Automatically generated as all
Value
objects inVALUES
wherecontrol == True
anddisplay == True
-
pvp.common.values.
PLOTS
= OrderedDict([(<ValueName.PRESSURE: 10>, <pvp.common.values.Value object>), (<ValueName.FLOWOUT: 11>, <pvp.common.values.Value object>), (<ValueName.FIO2: 8>, <pvp.common.values.Value object>)])¶ Values that can be plotted
Automatically generated as all
Value
objects inVALUES
whereplot == True
Message¶
Message objects that define the API between modules in the system.
SensorValues
are used to communicate sensor readings between the controller, GUI, and alarm managerControlSetting
is used to set ventilation controls from the GUI to the controller.
Classes:
|
Message containing ventilation control parameters. |
|
Class to save control values, analogous to SensorValues. |
|
Class to save derived values, analogous to SensorValues. |
|
Structured class for communicating sensor readings throughout PVP. |
|
alias of |
Functions:
|
Shallow copy operation on arbitrary Python objects. |
|
Initialize a logger for logging events. |
-
class
pvp.common.message.
SensorValues
(timestamp=None, loop_counter=None, breath_count=None, vals=typing.Union[NoneType, typing.Dict[ForwardRef('ValueName'), float]], **kwargs)[source]¶ Bases:
object
Structured class for communicating sensor readings throughout PVP.
Should be instantiated with each of the
SensorValues.additional_values
, and values for allValueName
s invalues.SENSOR
by passing them in thevals
kwarg. AnAssertionError
if an incomplete set of values is given.Values can be accessed either via attribute name (
SensorValues.PIP
) or like a dictionary (SensorValues['PIP']
)- Parameters
timestamp (float) – from time.time(). must be passed explicitly or as an entry in
vals
loop_counter (int) – number of control_module loops. must be passed explicitly or as an entry in
vals
breath_count (int) – number of breaths taken. must be passed explicitly or as an entry in
vals
vals (None, dict) – Dict of
{ValueName: float}
that contains current sensor readings. Can also be equivalently given askwargs
. if None, assumed values are being passed as kwargs, but an exception will be raised if they aren’t.**kwargs – sensor readings, must be in
pvp.values.SENSOR.keys
Methods:
__init__
([timestamp, loop_counter, …])- param timestamp
from time.time(). must be passed explicitly or as an entry in
vals
to_dict
()Return a dictionary of all sensor values and additional values
Attributes:
Additional attributes that are not
ValueName
s that are expected in each SensorValues message-
additional_values
= ('timestamp', 'loop_counter', 'breath_count')¶ Additional attributes that are not
ValueName
s that are expected in each SensorValues message
-
__init__
(timestamp=None, loop_counter=None, breath_count=None, vals=typing.Union[NoneType, typing.Dict[ForwardRef('ValueName'), float]], **kwargs)[source]¶ - Parameters
timestamp (float) – from time.time(). must be passed explicitly or as an entry in
vals
loop_counter (int) – number of control_module loops. must be passed explicitly or as an entry in
vals
breath_count (int) – number of breaths taken. must be passed explicitly or as an entry in
vals
vals (None, dict) – Dict of
{ValueName: float}
that contains current sensor readings. Can also be equivalently given askwargs
. if None, assumed values are being passed as kwargs, but an exception will be raised if they aren’t.**kwargs – sensor readings, must be in
pvp.values.SENSOR.keys
-
class
pvp.common.message.
ControlSetting
(name: pvp.common.values.ValueName, value: float = None, min_value: float = None, max_value: float = None, timestamp: float = None, range_severity: AlarmSeverity = None)[source]¶ Bases:
object
Message containing ventilation control parameters.
At least one of
value
,min_value
, ormax_value
must be given (unlikeSensorValues
which requires all fields to be present) – eg. in the case where one is setting alarm thresholds without changing the actual set valueWhen a parameter has multiple alarm limits for different alarm severities, the severity should be passed to
range_severity
- Parameters
name (
ValueName
) – Name of value being setvalue (float) – Value to set control
min_value (float) – Value to set control minimum (typically used for alarm thresholds)
max_value (float) – Value to set control maximum (typically used for alarm thresholds)
timestamp (float) –
time.time()
control message was generatedrange_severity (
AlarmSeverity
) – Some control settings have multiple limits for different alarm severities, this attr, when present, specifies which is being set.
Methods:
__init__
(name[, value, min_value, …])Message containing ventilation control parameters.
-
__init__
(name: pvp.common.values.ValueName, value: float = None, min_value: float = None, max_value: float = None, timestamp: float = None, range_severity: AlarmSeverity = None)[source]¶ Message containing ventilation control parameters.
At least one of
value
,min_value
, ormax_value
must be given (unlikeSensorValues
which requires all fields to be present) – eg. in the case where one is setting alarm thresholds without changing the actual set valueWhen a parameter has multiple alarm limits for different alarm severities, the severity should be passed to
range_severity
- Parameters
name (
ValueName
) – Name of value being setvalue (float) – Value to set control
min_value (float) – Value to set control minimum (typically used for alarm thresholds)
max_value (float) – Value to set control maximum (typically used for alarm thresholds)
timestamp (float) –
time.time()
control message was generatedrange_severity (
AlarmSeverity
) – Some control settings have multiple limits for different alarm severities, this attr, when present, specifies which is being set.
-
class
pvp.common.message.
ControlValues
(control_signal_in, control_signal_out)[source]¶ Bases:
object
Class to save control values, analogous to SensorValues.
Used by the controller to save waveform data in
DataLogger.store_waveform_data()
andControlModuleBase.__save_values`()
Key difference: SensorValues come exclusively from the sensors, ControlValues contains controller variables, i.e. control signals and controlled signals (the flows). :param control_signal_in: :param control_signal_out:
-
class
pvp.common.message.
DerivedValues
(timestamp, breath_count, I_phase_duration, pip_time, peep_time, pip, pip_plateau, peep, vte)[source]¶ Bases:
object
Class to save derived values, analogous to SensorValues.
Used by controller to store derived values (like PIP from Pressure) in
DataLogger.store_derived_data()
and inControlModuleBase.__analyze_last_waveform`()
Key difference: SensorValues come exclusively from the sensors, DerivedValues contain estimates of I_PHASE_DURATION, PIP_TIME, PEEP_time, PIP, PIP_PLATEAU, PEEP, and VTE. :param timestamp: :param breath_count: :param I_phase_duration: :param pip_time: :param peep_time: :param pip: :param pip_plateau: :param peep: :param vte:
Loggers¶
Logging functionality
There are two types of loggers:
loggers.init_logger()
creates a standardlogging.Logger
-based logging system for debugging and recording system events, and aloggers.DataLogger
- atables
- based class to store continuously measured sensor values.
Classes:
|
Structure for the hdf5-table for continuous waveform data; measured once per controller loop. |
|
Structure for the hdf5-table to store control commands. |
|
Structure for the hdf5-table to store derived quantities from waveform measurements. |
|
Class for logging numerical respiration data and control settings. Creates a hdf5 file with this general structure: / root |— waveforms (group) | |— time | pressure_data | flow_out | control_signal_in | control_signal_out | FiO2 | Cycle No. | |— controls (group) | |— (time, controllsignal) | |— derived_quantities (group) | |— (time, Cycle No, I_PHASE_DURATION, PIP_TIME, PEEP_time, PIP, PIP_PLATEAU, PEEP, VTE ) | |. |
|
The year, month and day arguments are required. |
Data:
list of strings, which loggers have been created already. |
Functions:
|
Initialize a logger for logging events. |
Adjust each logger’s |
-
pvp.common.loggers.
_LOGGERS
= ['pvp.common.prefs', 'pvp.alarm.alarm_manager']¶ list of strings, which loggers have been created already.
-
pvp.common.loggers.
init_logger
(module_name: str, log_level: int = None, file_handler: bool = True) → logging.Logger[source]¶ Initialize a logger for logging events.
To keep logs sensible, you should usually initialize the logger with the name of the module that’s using it, eg:
logger = init_logger(__name__)
If a logger has already been initialized (ie. its name is in
loggers._LOGGERS
, return that.otherwise configure and return the logger such that
its
LOGLEVEL
is set toprefs.LOGLEVEL
It formats logging messages with logger name, time, and logging level
if a file handler is specified (default), create a
logging.RotatingFileHandler
according to params set inprefs
- Parameters
module_name (str) – module name used to generate filename and name logger
log_level (int) – one of :var:`logging.DEBUG`, :var:`logging.INFO`, :var:`logging.WARNING`, or :var:`logging.ERROR`
file_handler (bool, str) – if
True
, (default), log in<logdir>/module_name.log
. ifFalse
, don’t log to disk.
- Returns
Logger 4 u 2 use
- Return type
-
pvp.common.loggers.
update_logger_sizes
()[source]¶ Adjust each logger’s
maxBytes
attribute so that the total across all loggers isprefs.LOGGING_MAX_BYTES
-
class
pvp.common.loggers.
DataLogger
(compression_level: int = 9)[source]¶ Bases:
object
Class for logging numerical respiration data and control settings. Creates a hdf5 file with this general structure:
/ root |— waveforms (group) | |— time | pressure_data | flow_out | control_signal_in | control_signal_out | FiO2 | Cycle No. | |— controls (group) | |— (time, controllsignal) | |— derived_quantities (group) | |— (time, Cycle No, I_PHASE_DURATION, PIP_TIME, PEEP_time, PIP, PIP_PLATEAU, PEEP, VTE ) | |
- Public Methods:
close_logfile(): Flushes, and closes the logfile. store_waveform_data(SensorValues): Takes data from SensorValues, but DOES NOT FLUSH store_controls(): Store controls in the same file? TODO: Discuss flush_logfile(): Flush the data into the file
Initialized the coontinuous numerical logger class.
- Parameters
compression_level (int, optional) – Compression level of the hdf5 file. Defaults to 9.
Methods:
__init__
([compression_level])Initialized the coontinuous numerical logger class.
Opens the hdf5 file and generates the file structure.
make sure that the file’s are not getting too large.
Flushes & closes the open hdf file.
This flushes the datapoints to the file.
load_file
([filename])This loads a hdf5 file, and returns data to the user as a dictionary with two keys: waveform_data and control_data
log2csv
([filename])Translates the compressed hdf5 into three csv files containing:
log2mat
([filename])Translates the compressed hdf5 into a matlab file containing a matlab struct. This struct has the same structure as the hdf5 file, but is not compressed. Use for any file: dl = DataLogger() dl.log2mat(filename) The file is saved at the same path as .mat file, where the content is represented as matlab-structs.
This rotates through filenames, similar to a ringbuffer, to make sure that the program does not run of of space/
store_control_command
(control_setting)Appends a datapoint to the event-table, derived from ControlSettings
store_derived_data
(derived_values)Appends a datapoint to the event-table, derived the continuous data (PIP, PEEP etc.)
store_waveform_data
(sensor_values, …)Appends a datapoint to the file for continuous logging of streaming data.
-
__init__
(compression_level: int = 9)[source]¶ Initialized the coontinuous numerical logger class.
- Parameters
compression_level (int, optional) – Compression level of the hdf5 file. Defaults to 9.
-
store_waveform_data
(sensor_values: SensorValues, control_values: ControlValues)[source]¶ Appends a datapoint to the file for continuous logging of streaming data. NOTE: Not flushed yet.
- Parameters
sensor_values (SensorValues) – SensorValues to be stored in the file.
control_values (ControlValues) – ControlValues to be stored in the file
-
store_control_command
(control_setting: ControlSetting)[source]¶ Appends a datapoint to the event-table, derived from ControlSettings
- Parameters
control_setting (ControlSetting) – ControlSettings object, the content of which should be stored
-
store_derived_data
(derived_values: DerivedValues)[source]¶ Appends a datapoint to the event-table, derived the continuous data (PIP, PEEP etc.)
- Parameters
derived_values (DerivedValues) – DerivedValues object, the content of which should be stored
-
flush_logfile
()[source]¶ This flushes the datapoints to the file. To be executed every other second, e.g. at the end of breath cycle.
-
rotation_newfile
()[source]¶ This rotates through filenames, similar to a ringbuffer, to make sure that the program does not run of of space/
-
load_file
(filename=None)[source]¶ This loads a hdf5 file, and returns data to the user as a dictionary with two keys: waveform_data and control_data
- Parameters
filename (str, optional) – Path to a hdf5-file. If none is given, uses currently open file. Defaults to None.
- Returns
Containing the data arranged as ` {“waveform_data”: waveform_data, “control_data”: control_data, “derived_data”: derived_data}`
- Return type
dictionary
-
log2mat
(filename=None)[source]¶ Translates the compressed hdf5 into a matlab file containing a matlab struct. This struct has the same structure as the hdf5 file, but is not compressed. Use for any file:
dl = DataLogger() dl.log2mat(filename)
The file is saved at the same path as .mat file, where the content is represented as matlab-structs.
- Parameters
filename (str, optional) – Path to a hdf5-file. If none is given, uses currently open file. Defaults to None.
-
log2csv
(filename=None)[source]¶ - Translates the compressed hdf5 into three csv files containing:
waveform_data (measurement once per cycle)
derived_quantities (PEEP, PIP etc.)
control_commands (control commands sent to the controller)
This approximates the structure contained in the hdf5 file. Use for any file:
dl = DataLogger() dl.log2csv(filename)
- Parameters
filename (str, optional) – Path to a hdf5-file. If none is given, uses currently open file. Defaults to None.
Prefs¶
Prefs set configurable parameters used throughout PVP.
See prefs._DEFAULTS
for description of all available parameters
Prefs are stored in a .json file, by default located at ~/pvp/prefs.json
. Prefs can be manually
changed by editing this file (when the system is not running, when the system is running use prefs.set_pref()
).
When any module in pvp is first imported, the prefs.init()
function is called
that
Makes any directories listed in
prefs._DIRECTORIES
Declares all prefs as their default values from
prefs._DEFAULTS
to ensure they are always definedLoads the existing
prefs.json
file and updates values from their defaults
Prefs can be gotten and set from anywhere in the system with prefs.get_pref()
and prefs.set_pref()
.
Prefs are stored in a multiprocessing.Manager
dictionary which makes these methods both thread- and process-safe.
Whenever a pref is set, the prefs.json
file is updated to reflect the new value, so preferences
are durable between runtimes.
Additional prefs
should be added by adding an entry in the prefs._DEFAULTS
dict rather than hardcoding them elsewhere in the program.
Data:
flag to indicate whether prefs have been loaded (and thus |
|
Declare all available parameters and set default values. |
|
Directories to ensure are created and added to prefs. |
|
Locks access to prefs_fn |
|
A |
|
The dict created by |
|
The |
Classes:
|
Functions:
|
Get global configuration value |
|
Initialize prefs. |
|
Load prefs from a .json prefs file, combining (and overwriting) any existing prefs, and then saves. |
ensures _DIRECTORIES are created and added to prefs. |
|
|
Dumps loaded prefs to |
|
Sets a pref in the manager and, if |
-
pvp.common.prefs.
_PREF_MANAGER
= <multiprocessing.managers.SyncManager object>¶ The
multiprocessing.Manager
that stores prefs during system operation
-
pvp.common.prefs.
_PREFS
= <DictProxy object, typeid 'dict'>¶ The dict created by
prefs._PREF_MANAGER
to store prefs.
-
pvp.common.prefs.
_LOGGER
= <Logger pvp.common.prefs (WARNING)>¶ A
logging.Logger
to log pref init and setting events
-
pvp.common.prefs.
_LOCK
= <Lock(owner=None)>¶ Locks access to prefs_fn
- Type
mp.Lock
-
pvp.common.prefs.
_DIRECTORIES
= {'DATA_DIR': '/home/docs/pvp/logs', 'LOG_DIR': '/home/docs/pvp/logs', 'VENT_DIR': '/home/docs/pvp'}¶ Directories to ensure are created and added to prefs.
VENT_DIR
: ~/pvp - base directory for user storageLOG_DIR
: ~/pvp/logs - for storage of event and alarm logsDATA_DIR
: ~/pvp/data - for storage of waveform data
-
pvp.common.prefs.
LOADED
= <Synchronized wrapper for c_bool(True)>¶ flag to indicate whether prefs have been loaded (and thus
set_pref()
should write to disk).uses a
multiprocessing.Value
to be thread and process safe.- Type
-
pvp.common.prefs.
_DEFAULTS
= {'BREATH_DETECTION': True, 'BREATH_PRESSURE_DROP': 4, 'CONTROLLER_LOOPS_UNTIL_UPDATE': 1, 'CONTROLLER_LOOP_UPDATE_TIME': 0.0, 'CONTROLLER_LOOP_UPDATE_TIME_SIMULATOR': 0.005, 'CONTROLLER_MAX_FLOW': 10, 'CONTROLLER_MAX_PRESSURE': 100, 'CONTROLLER_MAX_STUCK_SENSOR': 0.2, 'CONTROLLER_RINGBUFFER_SIZE': 100, 'COUGH_DURATION': 0.1, 'ENABLE_DIALOGS': True, 'ENABLE_WARNINGS': True, 'GUI_STATE_FN': 'gui_state.json', 'GUI_UPDATE_TIME': 0.05, 'HEARTBEAT_TIMEOUT': 0.02, 'LOGGING_MAX_BYTES': 2147483648, 'LOGGING_MAX_FILES': 5, 'LOGLEVEL': 'WARNING', 'PREFS_FN': None, 'TIMEOUT': 0.05, 'TIME_FIRST_START': None}¶ Declare all available parameters and set default values. If no default, set as None.
PREFS_FN
- absolute path to the prefs fileTIME_FIRST_START
- time when the program has been started for the first timeVENT_DIR
: ~/pvp - base directory for user storageLOG_DIR
: ~/pvp/logs - for storage of event and alarm logsDATA_DIR
: ~/pvp/data - for storage of waveform dataLOGGING_MAX_BYTES
: the total storage space for all loggers – each logger getsLOGGING_MAX_BYTES/len(loggers)
space (2GB by default)LOGGING_MAX_FILES
: number of files to split each logger’s logs across (default: 5)LOGLEVEL
: One of('DEBUG', 'INFO', 'WARNING', 'EXCEPTION')
that sets the minimum log level that is printed and written to diskTIMEOUT
: timeout used for timeout decorators on time-sensitive operations (in seconds, default 0.05)HEARTBEAT_TIMEOUT
: Time between heartbeats between GUI and controller after which contact is assumed to be lost (in seconds, default 0.02)GUI_STATE_FN
: Filename of gui control state file, relative toVENT_DIR
(default: gui_state.json)GUI_UPDATE_TIME
: Time between calls ofPVP_Gui.update_gui()
(in seconds, default: 0.05)ENABLE_DIALOGS
: Enable all GUI dialogs – set as False when testing on virtual frame buffer that doesn’t support them (default: True and should stay that way)ENABLE_WARNINGS
: Enable user warnings and value change confirmations (default: True)CONTROLLER_MAX_FLOW
: Maximum flow, above which the controller considers a sensor error (default: 10)CONTROLLER_MAX_PRESSURE
: Maximum pressure, above which the controller considers a sensor error (default: 100)CONTROLLER_MAX_STUCK_SENSOR
: Max amount of time (in s) before considering a sensor stuck (default: 0.2)CONTROLLER_LOOP_UPDATE_TIME
: Amount of time to sleep in between controller update times when usingControlModuleDevice
(default: 0.0)CONTROLLER_LOOP_UPDATE_TIME_SIMULATOR
: Amount of time to sleep in between controller updates when usingControlModuleSimulator
(default: 0.005)CONTROLLER_LOOPS_UNTIL_UPDATE
: Number of controller loops in between updating its externally-availableCOPY
attributes retrieved byControlModuleBase.get_sensor()
et alCONTROLLER_RINGBUFFER_SIZE
: Maximum number of breath cycle records to be kept in memory (default: 100)COUGH_DURATION
: Amount of time the high-pressure alarm limit can be exceeded and considered a cough (in seconds, default: 0.1)BREATH_PRESSURE_DROP
: Amount pressure can drop below set PEEP before being considered an autonomous breath when in breath detection modeBREATH_DETECTION
: Whether the controller should detect autonomous breaths in order to reset ventilation cycles (default: True)
-
pvp.common.prefs.
set_pref
(key: str, val)[source]¶ Sets a pref in the manager and, if
prefs.LOADED
is True, callsprefs.save_prefs()
- Parameters
key (str) – Name of pref key
val – Value to set
-
pvp.common.prefs.
load_prefs
(prefs_fn: str)[source]¶ Load prefs from a .json prefs file, combining (and overwriting) any existing prefs, and then saves.
Called on pvp import by
prefs.init()
Also initializes
prefs._LOGGER
Note
once this function is called,
set_pref()
will update the prefs file on disk. So ifload_prefs()
is called again at any point it should not change prefs.- Parameters
prefs_fn (str) – path of prefs.json
Unit Conversion¶
Functions that convert between units
Each function should accept a single float as an argument and return a single float
Used by the GUI to display values in different units. Widgets use these as
_convert_in
functions to convert units from the base unit to the displayed unit and_convert_out
functions to convert units from the displayed unit to the base unit.
Note
Unit conversions are cosmetic – values are always kept as the base unit internally (ie. cmH2O for pressure) and all that is changed is the displayed value in the GUI.
Functions:
|
Convert cmH2O to hPa |
|
Convert hPa to cmH2O |
|
Create a rounded string of a number that doesnt have trailing .0 when decimals = 0 |
utils¶
Exceptions:
Functions:
|
@contextmanager decorator. |
|
Initialize a logger for logging events. |
|
|
|
Defines a decorator for a 50ms timeout. |
fashion¶
Decorators for dangerous functions
Functions:
|
Initialize a logger for logging events. |
|
Wrapper to use as decorator, handle lock logic for a @property |
|
Values
Parameterize the values used by the GUI and Controller
Message
Message classes that formalize the communication API between the GUI and Controller
Loggers
Loggers for storing system events and ventilation data
Prefs
System configuration preferences
Unit Conversion
Functions to convert units used by the GUI!
Utils
Etc. Utility Functions
Fashion
Decorators (largely used by HAL)
pvp.io package¶
Subpackages¶
Submodules¶
pvp.io.hal module¶
Module for interacting with physical and/or simulated devices installed on the ventilator.
Classes:
|
Hardware Abstraction Layer for ventilator hardware. |
|
Abstract base Class describing generalized sensors. |
Functions:
|
Import a module. |
|
Safely evaluate an expression node or a string containing a Python expression. |
-
class
pvp.io.hal.
Hal
(config_file='pvp/io/config/devices.ini')[source]¶ Bases:
object
Hardware Abstraction Layer for ventilator hardware. Defines a common API for interacting with the sensors & actuators on the ventilator. The types of devices installed on the ventilator (real or simulated) are specified in a configuration file.
- Initializes HAL from config_file.
For each section in config_file, imports the class <type> from module <module>, and sets attribute self.<section> = <type>(**opts), where opts is a dict containing all of the options in <section> that are not <type> or <section>. For example, upon encountering the following entry in config_file.ini:
[adc] type = ADS1115 module = devices i2c_address = 0x48 i2c_bus = 1
- The Hal will:
- Import pvp.io.devices.ADS1115 (or ADS1015) as a local variable:
class_ = getattr(import_module(‘.devices’, ‘pvp.io’), ‘ADS1115’)
- Instantiate an ADS1115 object with the arguments defined in config_file and set it as an attribute:
self._adc = class_(pig=self.-pig,address=0x48,i2c_bus=1)
Note: RawConfigParser.optionxform() is overloaded here s.t. options are case sensitive (they are by default case insensitive). This is necessary due to the kwarg MUX which is so named for consistency with the config registry documentation in the ADS1115 datasheet. For example, A P4vMini pressure_sensor on pin A0 (MUX=0) of the ADC is passed arguments like:
- analog_sensor = AnalogSensor(
pig=self._pig, adc=self._adc, MUX=0, offset_voltage=0.25, output_span = 4.0, conversion_factor=2.54*20
)
Note: ast.literal_eval(opt) interprets integers, 0xFF, (a, b) etc. correctly. It does not interpret strings correctly, nor does it know ‘adc’ -> self._adc; therefore, these special cases are explicitly handled.
- Parameters
config_file (str) – Path to the configuration file containing the definitions of specific components on the ventilator machine. (e.g., config_file = “pvp/io/config/devices.ini”)
Methods:
__init__
([config_file])Initializes HAL from config_file.
Attributes:
Returns the pressure from the auxiliary pressure sensor, if so equipped.
The measured flow rate expiratory side.
The measured flow rate inspiratory side.
Returns the oxygen concentration from the primary oxygen sensor.
Returns the pressure from the primary pressure sensor.
The currently requested flow on the expiratory side as a proportion of the maximum.
The currently requested flow for the inspiratory proportional control valve as a proportion of maximum.
-
__init__
(config_file='pvp/io/config/devices.ini')[source]¶ - Initializes HAL from config_file.
For each section in config_file, imports the class <type> from module <module>, and sets attribute self.<section> = <type>(**opts), where opts is a dict containing all of the options in <section> that are not <type> or <section>. For example, upon encountering the following entry in config_file.ini:
[adc] type = ADS1115 module = devices i2c_address = 0x48 i2c_bus = 1
- The Hal will:
- Import pvp.io.devices.ADS1115 (or ADS1015) as a local variable:
class_ = getattr(import_module(‘.devices’, ‘pvp.io’), ‘ADS1115’)
- Instantiate an ADS1115 object with the arguments defined in config_file and set it as an attribute:
self._adc = class_(pig=self.-pig,address=0x48,i2c_bus=1)
Note: RawConfigParser.optionxform() is overloaded here s.t. options are case sensitive (they are by default case insensitive). This is necessary due to the kwarg MUX which is so named for consistency with the config registry documentation in the ADS1115 datasheet. For example, A P4vMini pressure_sensor on pin A0 (MUX=0) of the ADC is passed arguments like:
- analog_sensor = AnalogSensor(
pig=self._pig, adc=self._adc, MUX=0, offset_voltage=0.25, output_span = 4.0, conversion_factor=2.54*20
)
Note: ast.literal_eval(opt) interprets integers, 0xFF, (a, b) etc. correctly. It does not interpret strings correctly, nor does it know ‘adc’ -> self._adc; therefore, these special cases are explicitly handled.
- Parameters
config_file (str) – Path to the configuration file containing the definitions of specific components on the ventilator machine. (e.g., config_file = “pvp/io/config/devices.ini”)
-
property
pressure
¶ Returns the pressure from the primary pressure sensor.
-
property
oxygen
¶ Returns the oxygen concentration from the primary oxygen sensor.
-
property
aux_pressure
¶ Returns the pressure from the auxiliary pressure sensor, if so equipped. If a secondary pressure sensor is not defined, raises a RuntimeWarning.
-
property
flow_in
¶ The measured flow rate inspiratory side.
-
property
flow_ex
¶ The measured flow rate expiratory side.
-
property
setpoint_in
¶ The currently requested flow for the inspiratory proportional control valve as a proportion of maximum.
-
property
setpoint_ex
¶ The currently requested flow on the expiratory side as a proportion of the maximum.
Alarm¶
Alarm System Overview¶
Alarms are represented as
Alarm
objects, which are created and managed by theAlarm_Manager
.A collection of
Alarm_Rule
s define theCondition
s for raisingAlarm
s of differentAlarmSeverity
.The alarm manager is continuously fed
SensorValues
objects duringPVP_Gui.update_gui()
, which it uses tocheck()
each alarm rule.The alarm manager emits
Alarm
objects to thePVP_Gui.handle_alarm()
method.The alarm manager also updates alarm thresholds set as
Condition.depends
toPVP_Gui.limits_updated()
when control parameters are set (eg. updates theHIGH_PRESSURE
alarm to be triggered 15% above some setPIP
).
Alarm Modules¶
Alarm Manager¶
The alarm manager is responsible for checking the Alarm_Rule
s and maintaining
the Alarm
s active in the system.
Only one instance of the Alarm_Manager
can be created at once, and if it is
instantiated again, the existing object will be returned.
Classes:
|
Representation of alarm status and parameters |
|
An enumeration. |
|
An enumeration. |
The Alarm Manager |
|
|
|
|
Base class for specifying alarm test conditions |
|
Message containing ventilation control parameters. |
|
Structured class for communicating sensor readings throughout PVP. |
Functions:
|
Initialize a logger for logging events. |
|
Format a Python object into a pretty-printed representation. |
-
class
pvp.alarm.alarm_manager.
Alarm_Manager
[source]¶ The Alarm Manager
The alarm manager receives
SensorValues
from the GUI viaAlarm_Manager.update()
and emitsAlarm
s to methods given byAlarm_Manager.add_callback()
. When alarm limits are updated (ie. theAlarm_Rule
hasdepends
), it emits them to methods registered withAlarm_Manager.add_dependency_callback()
.On initialization, the alarm manager calls
Alarm_Manager.load_rules()
, which loads all rules defined inalarm.ALARM_RULES
.-
cleared_alarms
¶ of
AlarmType
s, alarms that have been cleared but have not dropped back into the ‘off’ range to enable re-raising- Type
-
snoozed_alarms
¶ of
AlarmType
s : times, alarms that should not be raised because they have been silenced for a period of time- Type
-
depends_callbacks
¶ When we
update_dependencies()
, we send back aControlSetting
with the new min/max- Type
-
rules
¶ A dict mapping
AlarmType
toAlarm_Rule
.- Type
If an Alarm_Manager already exists, when initing just return that one
Attributes:
Methods:
add_callback
(callback)Assert we’re being given a callable and add it to our list of callbacks.
add_dependency_callback
(callback)Assert we’re being given a callable and add it to our list of dependency_callbacks
check_rule
(rule, sensor_values)check()
the alarm rule, handle logic of raising, emitting, or lowering an alarm.call
Alarm_Manager.deactivate_alarm()
for all active alarms.deactivate_alarm
(alarm)Mark an alarm’s internal active flags and remove from
active_alarms
dismiss_alarm
(alarm_type[, duration])GUI or other object requests an alarm to be dismissed & deactivated
emit_alarm
(alarm_type, severity)Emit alarm (by calling all callbacks with it).
get_alarm_severity
(alarm_type)Get the severity of an Alarm
load_rule
(alarm_rule)Add the Alarm Rule to
Alarm_Manager.rules
and register any dependencies they have withAlarm_Manager.register_dependency()
Copy alarms from
alarm.ALARM_RULES
and callAlarm_Manager.load_rule()
for eachregister_alarm
(alarm)Be given an already created alarm and emit to callbacks.
register_dependency
(condition, dependency, …)Add dependency in a Condition object to be updated when values are changed
reset
()Reset all conditions, callbacks, and other stateful attributes and clear alarms
update
(sensor_values)Call
Alarm_Manager.check_rule()
for all rules inAlarm_Manager.rules
update_dependencies
(control_setting)Update Condition objects that update their value according to some control parameter
-
_instance
= None¶
-
active_alarms
: Dict[pvp.alarm.AlarmType, pvp.alarm.alarm.Alarm] = {}¶
-
logged_alarms
: List[pvp.alarm.alarm.Alarm] = []¶
-
dependencies
= {}¶
-
pending_clears
= []¶
-
cleared_alarms
= []¶
-
snoozed_alarms
= {}¶
-
callbacks
= []¶
-
depends_callbacks
= []¶
-
rules
= {}¶
-
logger
= <Logger pvp.alarm.alarm_manager (WARNING)>¶
-
load_rules
()[source]¶ Copy alarms from
alarm.ALARM_RULES
and callAlarm_Manager.load_rule()
for each
-
load_rule
(alarm_rule: pvp.alarm.rule.Alarm_Rule)[source]¶ Add the Alarm Rule to
Alarm_Manager.rules
and register any dependencies they have withAlarm_Manager.register_dependency()
- Parameters
alarm_rule (
Alarm_Rule
) – Alarm rule to be loaded
-
update
(sensor_values: pvp.common.message.SensorValues)[source]¶ Call
Alarm_Manager.check_rule()
for all rules inAlarm_Manager.rules
- Parameters
sensor_values (
SensorValues
) – New sensor values from the GUI
-
check_rule
(rule: pvp.alarm.rule.Alarm_Rule, sensor_values: pvp.common.message.SensorValues)[source]¶ check()
the alarm rule, handle logic of raising, emitting, or lowering an alarm.When alarms are dismissed, an
alarm.Alarm
is emitted withAlarmSeverity.OFF
.If the alarm severity has increased, emit a new alarm.
If the alarm severity has decreased and the alarm is not latched, emit a new alarm
If the alarm severity has decreased and the alarm is latched, check if the alarm has been manually dismissed, if it has emit a new alarm.
If a latched alarm has been manually dismissed previously and the alarm condition is now no longer met, dismiss the alarm.
- Parameters
rule (
Alarm_Rule
) – Alarm rule to checksensor_values (
SensorValues
) – sent by the GUI to check against alarm rule
-
emit_alarm
(alarm_type: pvp.alarm.AlarmType, severity: pvp.alarm.AlarmSeverity)[source]¶ Emit alarm (by calling all callbacks with it).
Note
This method emits and clears alarms – a cleared alarm is emitted with
AlarmSeverity.OFF
- Parameters
alarm_type (
AlarmType
) –severity (
AlarmSeverity
) –
-
deactivate_alarm
(alarm: (<enum 'AlarmType'>, <class 'pvp.alarm.alarm.Alarm'>))[source]¶ Mark an alarm’s internal active flags and remove from
active_alarms
Typically called internally when an alarm is being replaced by one of the same type but a different severity.
Note
This does not alert listeners that an alarm has been cleared, for that emit an alarm with AlarmSeverity.OFF
-
dismiss_alarm
(alarm_type: pvp.alarm.AlarmType, duration: float = None)[source]¶ GUI or other object requests an alarm to be dismissed & deactivated
GUI will wait until it receives an emit_alarm of severity == OFF to remove alarm widgets. If the alarm is not latched
If the alarm is latched, alarm_manager will not decrement alarm severity or emit OFF until a) the condition returns to OFF, and b) the user dismisses the alarm
-
get_alarm_severity
(alarm_type: pvp.alarm.AlarmType)[source]¶ Get the severity of an Alarm
- Parameters
alarm_type (
AlarmType
) – Alarm type to check- Returns
-
register_alarm
(alarm: pvp.alarm.alarm.Alarm)[source]¶ Be given an already created alarm and emit to callbacks.
Mostly used during testing for programmatically created alarms. Creating alarms outside of the Alarm_Manager is generally discouraged.
- Parameters
alarm (
Alarm
) –
-
register_dependency
(condition: pvp.alarm.condition.Condition, dependency: dict, severity: pvp.alarm.AlarmSeverity)[source]¶ Add dependency in a Condition object to be updated when values are changed
- Parameters
condition (dict) – Condition as defined in an
Alarm_Rule
dependency (dict) – either a (ValueName, attribute_name) or optionally also + transformation callable
severity (
AlarmSeverity
) – severity of dependency
-
update_dependencies
(control_setting: pvp.common.message.ControlSetting)[source]¶ Update Condition objects that update their value according to some control parameter
Call any
transform
functions on the attribute of the control setting specified in the depencency.Emit another
ControlSetting
describing the new max or min or the value.- Parameters
control_setting (
ControlSetting
) – Control setting that was changed
-
add_callback
(callback: Callable)[source]¶ Assert we’re being given a callable and add it to our list of callbacks.
- Parameters
callback (typing.Callable) – Callback that accepts a single argument of an
Alarm
-
add_dependency_callback
(callback: Callable)[source]¶ Assert we’re being given a callable and add it to our list of dependency_callbacks
- Parameters
callback (typing.Callable) – Callback that accepts a
ControlSetting
Returns:
-
clear_all_alarms
()[source]¶ call
Alarm_Manager.deactivate_alarm()
for all active alarms.
-
Alarm Objects¶
Alarm objects represent the state and severity of active alarms, but are otherwise intentionally quite featureless.
They are created and maintained by the Alarm_Manager
and sent to any listeners registered in Alarm_Manager.callbacks
.
Classes:
|
Representation of alarm status and parameters |
|
An enumeration. |
|
An enumeration. |
|
count(start=0, step=1) –> count object |
|
The year, month and day arguments are required. |
-
class
pvp.alarm.alarm.
Alarm
(alarm_type: pvp.alarm.AlarmType, severity: pvp.alarm.AlarmSeverity, start_time: float = None, latch: bool = True, cause: list = None, value=None, message=None)[source]¶ Representation of alarm status and parameters
Parameterized by a
Alarm_Rule
and managed byAlarm_Manager
- Parameters
alarm_type (
AlarmType
) – Type of alarmseverity (
AlarmSeverity
) – Severity of alarmstart_time (float) – Timestamp of alarm start, (as generated by
time.time()
cause (ValueName) – The
ValueName
that caused the alarm to be firedvalue (int, float) – optional - numerical value that generated the alarm
message (str) – optional - override default text generated by
AlarmManager
Methods:
__init__
(alarm_type, severity[, start_time, …])- param alarm_type
Type of alarm
If active, register an end time and set as
active == False
Returns:Attributes:
Alarm Type, property without setter to prevent change after instantiation
used to generate unique IDs for each alarm
Alarm Severity, property without setter to prevent change after instantiation
-
id_counter
= count(0)¶ used to generate unique IDs for each alarm
- Type
itertools.count
-
__init__
(alarm_type: pvp.alarm.AlarmType, severity: pvp.alarm.AlarmSeverity, start_time: float = None, latch: bool = True, cause: list = None, value=None, message=None)[source]¶ - Parameters
alarm_type (
AlarmType
) – Type of alarmseverity (
AlarmSeverity
) – Severity of alarmstart_time (float) – Timestamp of alarm start, (as generated by
time.time()
cause (ValueName) – The
ValueName
that caused the alarm to be firedvalue (int, float) – optional - numerical value that generated the alarm
message (str) – optional - override default text generated by
AlarmManager
-
property
severity
¶ Alarm Severity, property without setter to prevent change after instantiation
- Returns
-
property
alarm_type
¶ Alarm Type, property without setter to prevent change after instantiation
- Returns
Alarm Rule¶
One Alarm_Rule
is defined for each AlarmType
in ALARM_RULES
.
An alarm rule defines:
The conditions for raising different severities of an alarm
The dependencies between set values and alarm thresholds
The behavior of the alarm, specifically whether it is
latch
ed.
Example¶
As an example, we’ll define a LOW_PRESSURE
alarm with escalating severity. A LOW
severity alarm
will be raised when measured PIP
falls 10% below set PIP
, which will escalate to a
MEDIUM
severity alarm if measured PIP
falls 15% below set PIP
and the LOW
severity
alarm has been active for at least two breath cycles.
First we define the name and behavior of the alarm:
Alarm_Rule(
name = AlarmType.LOW_PRESSURE,
latch = False,
In this case, latch == False
means that the alarm will disappear (or be downgraded in severity)
whenever the conditions for that alarm are no longer met. If latch == True
, an alarm requires manual dismissal before
it is downgraded or disappears.
Next we’ll define a tuple of Condition
objects for LOW
and MEDIUM
severity objects.
Starting with the LOW
severity alarm:
conditions = (
(
AlarmSeverity.LOW,
condition.ValueCondition(
value_name=ValueName.PIP,
limit=VALUES[ValueName.PIP]['safe_range'][0],
mode='min',
depends={
'value_name': ValueName.PIP,
'value_attr': 'value',
'condition_attr': 'limit',
'transform': lambda x : x-(x*0.10)
})
),
# ... continued in next block
Each condition is a tuple of an (AlarmSeverity
, Condition
). In this case,
we use a ValueCondition
which tests whether a value is above or below a set 'max'
or 'min'
, respectively.
For the low severity LOW_PRESSURE
alarm, we test if ValueName.PIP
is below (mode='min'
) some limit
, which
is initialized as the low-end of PIP
’s safe range.
We also define a condition for updating the 'limit'
of the condition ('condition_attr' : 'limit'
), from the
ControlSetting.value`
field whenever PIP
is updated. Specifically, we set the limit
to be 10% less than the
set PIP
value by 10% with a lambda function (lambda x : x-(x*0.10)
).
Next, we define the MEDIUM
severity alarm condition:
(
AlarmSeverity.MEDIUM,
condition.ValueCondition(
value_name=ValueName.PIP,
limit=VALUES[ValueName.PIP]['safe_range'][0],
mode='min'
depends={
'value_name': ValueName.PIP,
'value_attr': 'value',
'condition_attr': 'limit',
'transform': lambda x: x - (x * 0.15)
},
) + \
condition.CycleAlarmSeverityCondition(
alarm_type = AlarmType.LOW_PRESSURE,
severity = AlarmSeverity.LOW,
n_cycles = 2
))
The first ValueCondition
is the same as in the LOW
alarm severity condition, except that it is
set 15% below PIP
.
A second CycleAlarmSeverityCondition
has been added (with +
) to the ValueCondition
When conditions are added together, they will only return True
(ie. trigger an alarm) if all of the conditions are met.
This condition checks that the LOW_PRESSURE
alarm has been active at a LOW
severity for at least two cycles.
Full source for this example and all alarm rules can be found here
Module Documentation¶
Class to declare alarm rules
Classes:
|
An enumeration. |
|
An enumeration. |
|
|
|
Canonical names of all values used in PVP. |
-
class
pvp.alarm.rule.
Alarm_Rule
(name: pvp.alarm.AlarmType, conditions, latch=True, technical=False)[source]¶ name of rule
conditions: ((alarm_type, (condition_1, condition_2)), …)
latch (bool): if True, alarm severity cannot be decremented until user manually dismisses
silencing/overriding rules
Methods:
check
(sensor_values)Check all of our
conditions
.reset
()Attributes:
Get all ValueNames whose alarm limits depend on this alarm rule :returns: list[ValueName]
Last Alarm Severity from
.check()
:returns:AlarmSeverity
Get all ValueNames specified as value_names in alarm conditions
-
property
severity
¶ Last Alarm Severity from
.check()
:returns:AlarmSeverity
-
property
depends
¶ Get all ValueNames whose alarm limits depend on this alarm rule :returns: list[ValueName]
-
property
value_names
¶ Get all ValueNames specified as value_names in alarm conditions
- Returns
list[ValueName]
Alarm Condition¶
Condition objects define conditions that can raise alarms. They are used by Alarm_Rule
s.
Each has to define a Condition.check()
method that accepts SensorValues
.
The method should return True
if the alarm condition is met, and False
otherwise.
Conditions can be added (+
) together to make compound conditions, and a single call to check
will only return true
if both conditions return true. If any condition in the chain returns false, evaluation is stopped and
the alarm is not raised.
Conditions can

Classes:
|
An enumeration. |
|
Alarm is above or below a certain severity. |
|
An enumeration. |
|
Base class for specifying alarm test conditions |
|
alarm goes out of range for a specific number of breath cycles |
|
Value goes out of range for a specific number of breath cycles |
|
Structured class for communicating sensor readings throughout PVP. |
|
value goes out of range for specific amount of time |
|
Value is greater or lesser than some max/min |
|
Canonical names of all values used in PVP. |
Functions:
-
class
pvp.alarm.condition.
Condition
(depends: dict = None, *args, **kwargs)[source]¶ Bases:
object
Base class for specifying alarm test conditions
Subclasses must define
Condition.check()
andConditino.reset()
Condition objects can be added together to create compound conditions.
- Parameters
Methods:
__init__
([depends])- param depends
check
(sensor_values)Every Condition subclass needs to define this method that accepts
SensorValues
and returns a booleanreset
()If a condition is stateful, need to provide some method of resetting the state
Attributes:
The active alarm manager, used to get status of alarms
-
property
manager
¶ The active alarm manager, used to get status of alarms
-
check
(sensor_values) → bool[source]¶ Every Condition subclass needs to define this method that accepts
SensorValues
and returns a boolean- Parameters
sensor_values (
SensorValues
) – SensorValues used to compute alarm status- Returns
bool
-
class
pvp.alarm.condition.
ValueCondition
(value_name: pvp.common.values.ValueName, limit: (<class 'int'>, <class 'float'>), mode: str, *args, **kwargs)[source]¶ Bases:
pvp.alarm.condition.Condition
Value is greater or lesser than some max/min
- Parameters
-
operator
¶ Either the less than or greater than operators, depending on whether mode is
'min'
or'max'
- Type
callable
Methods:
__init__
(value_name, limit, mode, *args, …)- param value_name
Which value to check
check
(sensor_values)Check that the relevant value in SensorValues is either greater or lesser than the limit
reset
()not stateful, do nothing.
Attributes:
One of ‘min’ or ‘max’, defines how the incoming sensor values are compared to the set value
-
__init__
(value_name: pvp.common.values.ValueName, limit: (<class 'int'>, <class 'float'>), mode: str, *args, **kwargs)[source]¶ - Parameters
-
operator
¶ Either the less than or greater than operators, depending on whether mode is
'min'
or'max'
- Type
callable
-
property
mode
¶ One of ‘min’ or ‘max’, defines how the incoming sensor values are compared to the set value
Returns:
-
check
(sensor_values)[source]¶ Check that the relevant value in SensorValues is either greater or lesser than the limit
- Parameters
sensor_values (
SensorValues
) –- Returns
bool
-
class
pvp.alarm.condition.
CycleValueCondition
(n_cycles: int, *args, **kwargs)[source]¶ Bases:
pvp.alarm.condition.ValueCondition
Value goes out of range for a specific number of breath cycles
- Parameters
n_cycles (int) – number of cycles required
-
_mid_check
¶ whether a value has left the acceptable range and we are counting consecutive breath cycles
- Type
- Parameters
-
operator
¶ Either the less than or greater than operators, depending on whether mode is
'min'
or'max'
- Type
callable
Methods:
check
(sensor_values)Check if outside of range, and then check if number of breath cycles have elapsed
reset
()Reset check status and start cycle
Attributes:
Number of cycles required
-
property
n_cycles
¶ Number of cycles required
-
class
pvp.alarm.condition.
TimeValueCondition
(time, *args, **kwargs)[source]¶ Bases:
pvp.alarm.condition.ValueCondition
value goes out of range for specific amount of time
Warning
Not implemented!
- Parameters
time (float) – number of seconds value must be out of range
*args –
**kwargs –
Methods:
__init__
(time, *args, **kwargs)- param time
number of seconds value must be out of range
check
(sensor_values)Check that the relevant value in SensorValues is either greater or lesser than the limit
reset
()not stateful, do nothing.
-
__init__
(time, *args, **kwargs)[source]¶ - Parameters
time (float) – number of seconds value must be out of range
*args –
**kwargs –
-
check
(sensor_values)[source]¶ Check that the relevant value in SensorValues is either greater or lesser than the limit
- Parameters
sensor_values (
SensorValues
) –- Returns
bool
-
class
pvp.alarm.condition.
AlarmSeverityCondition
(alarm_type: pvp.alarm.AlarmType, severity: pvp.alarm.AlarmSeverity, mode: str = 'min', *args, **kwargs)[source]¶ Bases:
pvp.alarm.condition.Condition
Alarm is above or below a certain severity.
Get alarm severity status from
Alarm_Manager.get_alarm_severity()
.- Parameters
alarm_type (
AlarmType
) – Alarm type to checkseverity (
AlarmSeverity
) – Alarm severity to check againstmode (str) –
one of ‘min’, ‘equals’, or ‘max’. ‘min’ returns true if the alarm is at least this value (note the difference from ValueCondition which returns true if the alarm is less than..) and vice versa for ‘max’.
Note
’min’ and ‘max’ use >= and <= rather than > and <
*args –
**kwargs –
Methods:
__init__
(alarm_type, severity[, mode])Alarm is above or below a certain severity.
check
(sensor_values)Every Condition subclass needs to define this method that accepts
SensorValues
and returns a booleanreset
()If a condition is stateful, need to provide some method of resetting the state
Attributes:
‘min’ returns true if the alarm is at least this value (note the difference from ValueCondition which returns true if the alarm is less than..) and vice versa for ‘max’.
-
__init__
(alarm_type: pvp.alarm.AlarmType, severity: pvp.alarm.AlarmSeverity, mode: str = 'min', *args, **kwargs)[source]¶ Alarm is above or below a certain severity.
Get alarm severity status from
Alarm_Manager.get_alarm_severity()
.- Parameters
alarm_type (
AlarmType
) – Alarm type to checkseverity (
AlarmSeverity
) – Alarm severity to check againstmode (str) –
one of ‘min’, ‘equals’, or ‘max’. ‘min’ returns true if the alarm is at least this value (note the difference from ValueCondition which returns true if the alarm is less than..) and vice versa for ‘max’.
Note
’min’ and ‘max’ use >= and <= rather than > and <
*args –
**kwargs –
-
property
mode
¶ ‘min’ returns true if the alarm is at least this value (note the difference from ValueCondition which returns true if the alarm is less than..) and vice versa for ‘max’.
Note
‘min’ and ‘max’ use >= and <= rather than > and <
- Returns
one of ‘min’, ‘equals’, or ‘max’.
- Return type
-
check
(sensor_values)[source]¶ Every Condition subclass needs to define this method that accepts
SensorValues
and returns a boolean- Parameters
sensor_values (
SensorValues
) – SensorValues used to compute alarm status- Returns
bool
-
class
pvp.alarm.condition.
CycleAlarmSeverityCondition
(n_cycles, *args, **kwargs)[source]¶ Bases:
pvp.alarm.condition.AlarmSeverityCondition
alarm goes out of range for a specific number of breath cycles
Todo
note that this is exactly the same as CycleValueCondition. Need to do the multiple inheritance thing
-
_mid_check
¶ whether a value has left the acceptable range and we are counting consecutive breath cycles
- Type
Alarm is above or below a certain severity.
Get alarm severity status from
Alarm_Manager.get_alarm_severity()
.- Parameters
alarm_type (
AlarmType
) – Alarm type to checkseverity (
AlarmSeverity
) – Alarm severity to check againstmode (str) –
one of ‘min’, ‘equals’, or ‘max’. ‘min’ returns true if the alarm is at least this value (note the difference from ValueCondition which returns true if the alarm is less than..) and vice versa for ‘max’.
Note
’min’ and ‘max’ use >= and <= rather than > and <
*args –
**kwargs –
Methods:
check
(sensor_values)Every Condition subclass needs to define this method that accepts
SensorValues
and returns a booleanreset
()If a condition is stateful, need to provide some method of resetting the state
Attributes:
-
property
n_cycles
¶
-
check
(sensor_values)[source]¶ Every Condition subclass needs to define this method that accepts
SensorValues
and returns a boolean- Parameters
sensor_values (
SensorValues
) – SensorValues used to compute alarm status- Returns
bool
-
Alarm Manager
Computes alarm logic and emits alarms to the GUI
Alarm
Objects used to represent alarms
Alarm Rule
Define conditions for triggering alarms and their behavior
Condition
Objects to check for alarm state
Main Alarm Module¶
Data:
Definitions of all |
Classes:
|
Representation of alarm status and parameters |
|
An enumeration. |
|
An enumeration. |
|
The Alarm Manager |
|
|
|
Generic enumeration. |
|
Enum where members are also (and must be) ints |
|
Canonical names of all values used in PVP. |
|
Instances are replaced with an appropriate value in Enum class suites. |
|
alias of |
-
class
pvp.alarm.
AlarmType
(value)[source]¶ An enumeration.
Attributes:
Replace
.name
underscores with spaces-
LOW_PRESSURE
= 1¶
-
HIGH_PRESSURE
= 2¶
-
LOW_VTE
= 3¶
-
HIGH_VTE
= 4¶
-
LOW_PEEP
= 5¶
-
HIGH_PEEP
= 6¶
-
LOW_O2
= 7¶
-
HIGH_O2
= 8¶
-
OBSTRUCTION
= 9¶
-
LEAK
= 10¶
-
SENSORS_STUCK
= 11¶
-
BAD_SENSOR_READINGS
= 12¶
-
MISSED_HEARTBEAT
= 13¶
-
property
human_name
¶ Replace
.name
underscores with spaces
-
-
class
pvp.alarm.
AlarmSeverity
(value)[source]¶ An enumeration.
Attributes:
-
HIGH
= 3¶
-
MEDIUM
= 2¶
-
LOW
= 1¶
-
OFF
= 0¶
-
TECHNICAL
= -1¶
-
-
pvp.alarm.
ALARM_RULES
= OrderedDict([(<AlarmType.LOW_PRESSURE: 1>, <pvp.alarm.rule.Alarm_Rule object>), (<AlarmType.HIGH_PRESSURE: 2>, <pvp.alarm.rule.Alarm_Rule object>), (<AlarmType.LOW_VTE: 3>, <pvp.alarm.rule.Alarm_Rule object>), (<AlarmType.HIGH_VTE: 4>, <pvp.alarm.rule.Alarm_Rule object>), (<AlarmType.LOW_PEEP: 5>, <pvp.alarm.rule.Alarm_Rule object>), (<AlarmType.HIGH_PEEP: 6>, <pvp.alarm.rule.Alarm_Rule object>), (<AlarmType.LOW_O2: 7>, <pvp.alarm.rule.Alarm_Rule object>), (<AlarmType.HIGH_O2: 8>, <pvp.alarm.rule.Alarm_Rule object>)])¶ Definitions of all
Alarm_Rule
s used by theAlarm_Manager
See definitions here
coordinator module¶
Submodules¶
coordinator¶
Classes:
|
Representation of alarm status and parameters |
|
Message containing ventilation control parameters. |
|
|
|
|
|
|
|
|
|
Structured class for communicating sensor readings throughout PVP. |
|
Canonical names of all values used in PVP. |
Data:
|
The central part of internal API. |
|
The central part of internal API. |
Functions:
|
|
|
|
|
Initialize a logger for logging events. |
-
class
pvp.coordinator.coordinator.
CoordinatorBase
(sim_mode=False)[source]¶ Bases:
object
Methods:
get_alarms
()get_control
(control_setting_name)get_sensors
()get_target_waveform
()is_running
()kill
()set_breath_detection
(breath_detection)set_control
(control_setting)start
()stop
()-
get_sensors
() → pvp.common.message.SensorValues[source]¶
-
get_alarms
() → Union[None, Tuple[pvp.alarm.alarm.Alarm]][source]¶
-
set_control
(control_setting: pvp.common.message.ControlSetting)[source]¶
-
get_control
(control_setting_name: pvp.common.values.ValueName) → pvp.common.message.ControlSetting[source]¶
-
-
class
pvp.coordinator.coordinator.
CoordinatorLocal
(sim_mode=False)[source]¶ Bases:
pvp.coordinator.coordinator.CoordinatorBase
- Parameters
sim_mode –
-
_is_running
¶ .set()
when thread should stop- Type
Methods:
__init__
([sim_mode])- param sim_mode
get_alarms
()get_control
(control_setting_name)get_sensors
()get_target_waveform
()is_running
()Test whether the whole system is running
kill
()set_breath_detection
(breath_detection)set_control
(control_setting)start
()Start the coordinator.
stop
()Stop the coordinator.
-
__init__
(sim_mode=False)[source]¶ - Parameters
sim_mode –
-
_is_running
¶ .set()
when thread should stop- Type
-
get_sensors
() → pvp.common.message.SensorValues[source]¶
-
get_alarms
() → Union[None, Tuple[pvp.alarm.alarm.Alarm]][source]¶
-
set_control
(control_setting: pvp.common.message.ControlSetting)[source]¶
-
get_control
(control_setting_name: pvp.common.values.ValueName) → pvp.common.message.ControlSetting[source]¶
-
class
pvp.coordinator.coordinator.
CoordinatorRemote
(sim_mode=False)[source]¶ Bases:
pvp.coordinator.coordinator.CoordinatorBase
Methods:
get_alarms
()get_control
(control_setting_name)get_sensors
()get_target_waveform
()is_running
()Test whether the whole system is running
kill
()Stop the coordinator and end the whole program
set_breath_detection
(breath_detection)set_control
(control_setting)start
()Start the coordinator.
stop
()Stop the coordinator.
-
get_sensors
() → pvp.common.message.SensorValues[source]¶
-
get_alarms
() → Union[None, Tuple[pvp.alarm.alarm.Alarm]][source]¶
-
set_control
(control_setting: pvp.common.message.ControlSetting)[source]¶
-
get_control
(control_setting_name: pvp.common.values.ValueName) → pvp.common.message.ControlSetting[source]¶
-
-
pvp.coordinator.coordinator.
get_coordinator
(single_process=False, sim_mode=False) → pvp.coordinator.coordinator.CoordinatorBase[source]¶
ipc¶
Classes:
|
Simple XML-RPC server. |
Functions:
|
|
|
Initialize a logger for logging events. |
|
|
|
|
|
Changelog¶
Version 0.0¶
v0.0.2 (April xxth, 2020)¶
Refactored gui into a module, splitting
widgets
,styles
, anddefaults
.
v0.0.1 (April 12th, 2020)¶
Added changelog
Moved requirements for building docs to requirements_docs.txt so regular program reqs are a bit lighter.
added autosummaries
added additional resources & documentation files, with examples for adding external files like pdfs
v0.0.0 (April 12th, 2020)¶
Example of a changelog entry!!!
We fixed this
and this
and this
Warning
but we didn’t do this thing
Todo
and we still have to do this other thing.
Contributing¶
Building the Docs¶
A very brief summary…
Docs are configured to be built from
_docs
intodocs
.The main page is
index.rst
which links to the existing modulesTo add a new page, you can create a new
.rst
file if you are writing with Restructuredtext , or a .md file if you are writing with markdown.
Local Build¶
pip install -r requirements.txt
cd _docs
make html
Documentation will be generated into docs
Advertisement :)
pica - high quality and fast image resize in browser.
babelfish - developer friendly i18n with plurals support and easy syntax.
You will like those projects!
h1 Heading 8-)¶
Blockquotes¶
Blockquotes can also be nested…
…by using additional greater-than signs right next to each other…
…or with spaces between arrows.
Lists¶
Unordered
Create a list by starting a line with
+
,-
, or*
Sub-lists are made by indenting 2 spaces:
Marker character change forces new list start:
Ac tristique libero volutpat at
Facilisis in pretium nisl aliquet
Nulla volutpat aliquam velit
Very easy!
Ordered
Lorem ipsum dolor sit amet
Consectetur adipiscing elit
Integer molestie lorem at massa
You can use sequential numbers…
…or keep all the numbers as
1.
Code¶
Inline code
Indented code
// Some comments
line 1 of code
line 2 of code
line 3 of code
Block code “fences”
Sample text here...
Syntax highlighting
var foo = function (bar) {
return bar++;
};
console.log(foo(5));
Images¶
Minion
Stormtroopocat
Like links, Images also have a footnote style syntax
Alt text
With a reference later in the document defining the URL location: