Skip to main content

ReadHandler

The ReadHandler interface receives measurement values from the outstation. Each association has one, which you supply when you create it.

Where values come from​

Everything the association reads goes to its ReadHandler, except a read you send with your own handler:

The begin_fragment and end_fragment callbacks receive a read type that tells you which of these the values came from:

Read typeValues from
UnsolicitedUnsolicited responses
StartupIntegrityThe integrity scans the master runs on its own: at startup, after the outstation restarts, and after an event buffer overflow
PeriodicPollYour polls, and the automatic event scans
SinglePollOne-time reads, including read_with_handler

See Scheduling for when the master runs integrity and event scans on its own.

Callbacks​

For every READ response or unsolicited fragment the master accepts, even one without any objects, ReadHandler callbacks are executed in the following order:

  1. ReadHandler::begin_fragment
  2. ReadHandler::handle_<TYPE> for each object header in the message
  3. ReadHandler::end_fragment

The begin and end methods receive the read type and the full header of the response.

To determine if a measurement is a static value or an event, use the HeaderInfo::is_event field.

Each value has a set of associated flags. In Rust, check for a flag with Flags::is_set and the constants on Flags, such as Flags::ONLINE. In the bindings, compare the Flags::value field with the constants defined in Flag.

tip

It's common to maintain a list of measurement values in your ReadHandler implementation. You can then use the end_fragment method as a trigger to publish them. Since responses may consist of multiple fragments, you can inspect the ResponseHeader.Control.Fin field to determine if the current fragment is the final fragment in a response series.

The ReadHandler also receives device attributes.

Examples​

The example below shows an implementation of a ReadHandler that prints every value and device attribute it receives to the console.

Show the full example
struct ExampleReadHandler;

impl ReadHandler for ExampleReadHandler {
fn begin_fragment(&mut self, read_type: ReadType, header: ResponseHeader) -> MaybeAsync<()> {
println!(
"begin fragment: {read_type:?} (broadcast: {})",
header.iin.iin1.get_broadcast()
);
MaybeAsync::ready(())
}

fn end_fragment(&mut self, _read_type: ReadType, _header: ResponseHeader) -> MaybeAsync<()> {
println!("end fragment");
MaybeAsync::ready(())
}

fn handle_binary_input(
&mut self,
info: HeaderInfo,
iter: &mut dyn Iterator<Item = (BinaryInput, u16)>,
) {
print_header("Binary inputs", info);
for (x, idx) in iter {
print_point("BI", idx, x.value, x.flags, x.time);
}
}

fn handle_double_bit_binary_input(
&mut self,
info: HeaderInfo,
iter: &mut dyn Iterator<Item = (DoubleBitBinaryInput, u16)>,
) {
print_header("Double-bit binary inputs", info);
for (x, idx) in iter {
print_point("DBBI", idx, format!("{:?}", x.value), x.flags, x.time);
}
}

fn handle_binary_output_status(
&mut self,
info: HeaderInfo,
iter: &mut dyn Iterator<Item = (BinaryOutputStatus, u16)>,
) {
print_header("Binary output statuses", info);
for (x, idx) in iter {
print_point("BOS", idx, x.value, x.flags, x.time);
}
}

fn handle_counter(&mut self, info: HeaderInfo, iter: &mut dyn Iterator<Item = (Counter, u16)>) {
print_header("Counters", info);
for (x, idx) in iter {
print_point("C", idx, x.value, x.flags, x.time);
}
}

fn handle_frozen_counter(
&mut self,
info: HeaderInfo,
iter: &mut dyn Iterator<Item = (FrozenCounter, u16)>,
) {
print_header("Frozen counters", info);
for (x, idx) in iter {
print_point("FC", idx, x.value, x.flags, x.time);
}
}

fn handle_analog_input(
&mut self,
info: HeaderInfo,
iter: &mut dyn Iterator<Item = (AnalogInput, u16)>,
) {
print_header("Analog inputs", info);
for (x, idx) in iter {
print_point("AI", idx, x.value, x.flags, x.time);
}
}

fn handle_frozen_analog_input(
&mut self,
info: HeaderInfo,
iter: &mut dyn Iterator<Item = (FrozenAnalogInput, u16)>,
) {
print_header("Frozen analog inputs", info);
for (x, idx) in iter {
print_point("FAI", idx, x.value, x.flags, x.time);
}
}

fn handle_analog_input_dead_band(
&mut self,
info: HeaderInfo,
iter: &mut dyn Iterator<Item = (AnalogInputDeadBand, u16)>,
) {
print_header("Analog input deadbands", info);
for (x, idx) in iter {
let value = match x {
AnalogInputDeadBand::U16(v) => f64::from(v),
AnalogInputDeadBand::U32(v) => f64::from(v),
AnalogInputDeadBand::F32(v) => f64::from(v),
};
println!("AIDB {idx}: value={value}");
}
}

fn handle_analog_output_status(
&mut self,
info: HeaderInfo,
iter: &mut dyn Iterator<Item = (AnalogOutputStatus, u16)>,
) {
print_header("Analog output statuses", info);
for (x, idx) in iter {
print_point("AOS", idx, x.value, x.flags, x.time);
}
}

fn handle_binary_output_command_event(
&mut self,
info: HeaderInfo,
iter: &mut dyn Iterator<Item = (BinaryOutputCommandEvent, u16)>,
) {
print_header("Binary output command events", info);
for (x, idx) in iter {
println!(
"BOCE {idx}: commanded state={} status={:?} time={}",
x.commanded_state,
x.status,
format_time(x.time)
);
}
}

fn handle_analog_output_command_event(
&mut self,
info: HeaderInfo,
iter: &mut dyn Iterator<Item = (AnalogOutputCommandEvent, u16)>,
) {
print_header("Analog output command events", info);
for (x, idx) in iter {
let value = match x.commanded_value {
AnalogCommandValue::I16(v) => f64::from(v),
AnalogCommandValue::I32(v) => f64::from(v),
AnalogCommandValue::F32(v) => f64::from(v),
AnalogCommandValue::F64(v) => v,
};
println!(
"AOCE {idx}: commanded value={value} status={:?} time={}",
x.status,
format_time(x.time)
);
}
}

fn handle_unsigned_integer(
&mut self,
info: HeaderInfo,
iter: &mut dyn Iterator<Item = (UnsignedInteger, u16)>,
) {
print_header("Unsigned integers", info);
for (x, idx) in iter {
println!("UI {idx}: value={}", x.value);
}
}

fn handle_octet_string<'a>(
&mut self,
info: HeaderInfo,
iter: &'a mut dyn Iterator<Item = (&'a [u8], u16)>,
) {
print_header("Octet strings", info);
for (x, idx) in iter {
println!("OS {idx}: value={x:02X?}");
}
}

fn handle_device_attribute(&mut self, info: HeaderInfo, attr: AnyAttribute) {
print_header("Device attributes", info);
match attr {
AnyAttribute::Known(attr) => match attr {
KnownAttribute::AttributeList(var, list) => {
print_attribute(0, var.variation(), format_variation_list(list))
}
KnownAttribute::String(var, value) => print_attribute(0, var.variation(), value),
KnownAttribute::Float(var, FloatType::F32(value)) => {
print_attribute(0, var.variation(), value)
}
KnownAttribute::Float(var, FloatType::F64(value)) => {
print_attribute(0, var.variation(), value)
}
KnownAttribute::UInt(var, value) => print_attribute(0, var.variation(), value),
KnownAttribute::Bool(var, value) => print_attribute(0, var.variation(), value),
KnownAttribute::OctetString(var, value) => {
print_attribute(0, var.variation(), format!("{value:02X?}"))
}
KnownAttribute::DNP3Time(var, value) => {
print_attribute(0, var.variation(), value.raw_value())
}
},
AnyAttribute::Other(attr) => {
let value = match attr.value {
AttrValue::VisibleString(v) => v.to_string(),
AttrValue::UnsignedInt(v) => v.to_string(),
AttrValue::SignedInt(v) => v.to_string(),
AttrValue::FloatingPoint(FloatType::F32(v)) => v.to_string(),
AttrValue::FloatingPoint(FloatType::F64(v)) => v.to_string(),
AttrValue::OctetString(v) | AttrValue::BitString(v) => format!("{v:02X?}"),
AttrValue::Dnp3Time(v) => v.raw_value().to_string(),
AttrValue::AttrList(list) => format_variation_list(list),
};
print_attribute(attr.set.value(), attr.variation, value)
}
}
}

fn handle_abs_time(&mut self, info: HeaderInfo, time: Timestamp) {
print_header("Absolute time", info);
println!("time={}", time.raw_value());
}
}

fn print_header(name: &str, info: HeaderInfo) {
println!("{name} ({}, {})", info.qualifier, info.variation);
}

fn print_point(
name: &str,
index: u16,
value: impl std::fmt::Display,
flags: Flags,
time: Option<Time>,
) {
println!(
"{name} {index}: value={value} flags=0x{:02X} time={}",
flags.value,
format_time(time)
);
}

fn format_time(time: Option<Time>) -> String {
match time {
Some(Time::Synchronized(t)) => format!("{} (synchronized)", t.raw_value()),
Some(Time::Unsynchronized(t)) => format!("{} (unsynchronized)", t.raw_value()),
None => "none".to_string(),
}
}

fn format_variation_list(list: VariationList) -> String {
let items: Vec<String> = list
.iter()
.map(|item| {
let writable = if item.properties.is_writable() {
" (writable)"
} else {
""
};
format!("{}{writable}", item.variation)
})
.collect();
format!("[{}]", items.join(", "))
}

fn print_attribute(set: u8, variation: u8, value: impl std::fmt::Display) {
println!("attribute set={set} variation={variation} value={value}");
}