Add sensors and actuators#

.sensor() adds a sensor and the property it observes. .actuator() adds a command property and the actuator that carries it out. Both work on equipment, named points, and connection handles.

from plumb import Model, connect
from plumb.qudt import quantity_kinds, units
from plumb.watr import LevelSensor, Pump, Tank, enums
from plumb.watr.drinking_water import ChlorinationStage, RapidSandFiltrationStage

values = enums.values
model = Model("urn:example:instruments#")
with model:
    sand_filter = RapidSandFiltrationStage("sand-filter")
    chlorination = ChlorinationStage("chlorine-contact")
    clearwell = Tank("clearwell")
    high_service = Pump("high-service-pump")

connect(sand_filter.filtered_water, chlorination.inlet, name="filtered-water")
connect(chlorination.outlet, clearwell, name="disinfected-water")
finished = connect(clearwell, high_service, name="finished-water")

Add a sensor#

Call .sensor() on what is measured. A turbidimeter on the filter outlet:

turbidimeter = sand_filter.filtered_water.sensor(
    "filter-effluent-turbidity",
    kind=quantity_kinds.Turbidity,
    unit=units.NTU,
)
turbidimeter, turbidimeter.observes
(<TurbidityMeter 'filter-effluent-turbidity'>,
 <QuantifiableObservableProperty 'filter-effluent-turbidity-value'>)

The sensor class is the most specific one the package defines for the quantity kind: TurbidityMeter, FlowSensor, ConcentrationSensor, ConductivitySensor, TemperatureSensor, or PressureSensor in WaTr. The observed property is named <name>-value; of_medium, of_substance, and value go to it.

Measure a connection#

A chlorine analyzer on a connection handle:

analyzer = finished.sensor(
    "chlorine-residual",
    kind=quantity_kinds.MassConcentration,
    unit=units.MilliGM_PER_L,
    of_substance=values.Constituent_ChlorineResidual,
)
analyzer.has_observation_location
<ConnectionHandle finished-water: clearwell -> high-service-pump>

Resolution replaces the handle with the concrete pipe.

Choose the sensor class#

A kind with no specific class gives a plain Sensor. Pass sensor_class=:

level = clearwell.sensor(
    "clearwell-level",
    kind=quantity_kinds.Length,
    unit=units.M,
    sensor_class=LevelSensor,
)
level
<LevelSensor 'clearwell-level'>

Observe a status#

An enumeration kind gives an EnumeratedObservableProperty instead of a number:

running = high_service.sensor("high-service-running", kind=values.Binary_OnOff)
running.observes
<EnumeratedObservableProperty 'high-service-running-value'>

Add an actuator#

.actuator() creates a <name>-command property and an Actuator that actuates the equipment. Both are actuated_by_property the command:

speed = high_service.actuator(
    "high-service-speed", kind=quantity_kinds.Frequency, unit=units.HZ
)
start = high_service.actuator(
    "high-service-start", kind=values.Binary_OnOff, value=values.OnOff_Off
)
list(high_service.actuated_by_property)
[<QuantifiableActuatableProperty 'high-service-speed-command'>,
 <EnumeratedActuatableProperty 'high-service-start-command'>]

A quantity kind gives a QuantifiableActuatableProperty. An enumeration kind gives an EnumeratedActuatableProperty; value= must be a member.

Resolve and draw#

resolved = model.compile()
analyzer = resolved["chlorine-residual"]
analyzer.has_observation_location is resolved.connection(finished)
True

The observed property takes the pipe’s medium; see Attach quantities:

analyzer.observes.of_medium
<enum Fluid-Water>
from plumb import flowsheet

flowsheet(resolved)
../_images/3b645c6b402861e43d83e35d90e6cd8ad91dd1324aa2f4e1dfa13c8c3ceaea74.svg

Build a sensor by hand#

.sensor() is shorthand for:

from plumb.watr import TurbidityMeter

turbidity = filtered.quantity(
    "filtered-turbidity-value",
    0.1,
    unit=units.NTU,
    kind=quantity_kinds.Turbidity,
    capability="observable",
)
TurbidityMeter(
    "filtered-turbidity",
    has_observation_location=filtered,
    observes=turbidity,
)

has_observation_location and has_reference_location accept handles, ports, and named points. Other relations need concrete objects.