ReadHandler
The ReadHandler
interface is how the measurement data received from the outstation is passed to your application code. This callback interface is specified
for each association you create and is invoked for both unsolicited responses and responses to polls.
When a response is parsed and determined to carry measurement data, ReadHandler
callbacks are executed in the following order:
ReadHandler::begin_fragment
ReadHandler::handle_<TYPE>
for each object header in the messageReadHandler::end_fragment
The begin/end methods provide useful information, including:
- The full header of the response
- Context regarding what triggered the response, such as unsolicited, startup integrity scan, periodic poll, etc.
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. You can check the presence of a flag by using the Flags::value
field and comparing
it with the constants defined in Flag
.
It's common to maintain a list of measurement values in your ReadHandler
implementation. You can then use the endFragment
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.
It is possible to send a read request with a specific ReadHandler
by using the MasterChannel::read_with_handler()
method.
All the response data associated with the request will be forwarded to the ReadHandler
specified as an argument instead of
going to the handler specified at the creation of the association.
- Rust
- C
- C++
- Java
- C#
impl ReadHandler for ExampleReadHandler {
fn begin_fragment(&mut self, _read_type: ReadType, header: ResponseHeader) -> MaybeAsync<()> {
println!(
"Beginning fragment (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)>,
) {
println!("Binary Inputs:");
println!("Qualifier: {}", info.qualifier);
println!("Variation: {}", info.variation);
for (x, idx) in iter {
println!(
"BI {}: Value={} Flags={:#04X} Time={:?}",
idx, x.value, x.flags.value, x.time
);
}
}
fn handle_double_bit_binary_input(
&mut self,
info: HeaderInfo,
iter: &mut dyn Iterator<Item = (DoubleBitBinaryInput, u16)>,
) {
println!("Double Bit Binary Inputs:");
println!("Qualifier: {}", info.qualifier);
println!("Variation: {}", info.variation);
for (x, idx) in iter {
println!(
"DBBI {}: Value={} Flags={:#04X} Time={:?}",
idx, x.value, x.flags.value, x.time
);
}
}
fn handle_binary_output_status(
&mut self,
info: HeaderInfo,
iter: &mut dyn Iterator<Item = (BinaryOutputStatus, u16)>,
) {
println!("Binary Output Statuses:");
println!("Qualifier: {}", info.qualifier);
println!("Variation: {}", info.variation);
for (x, idx) in iter {
println!(
"BOS {}: Value={} Flags={:#04X} Time={:?}",
idx, x.value, x.flags.value, x.time
);
}
}
fn handle_counter(&mut self, info: HeaderInfo, iter: &mut dyn Iterator<Item = (Counter, u16)>) {
println!("Counters:");
println!("Qualifier: {}", info.qualifier);
println!("Variation: {}", info.variation);
for (x, idx) in iter {
println!(
"Counter {}: Value={} Flags={:#04X} Time={:?}",
idx, x.value, x.flags.value, x.time
);
}
}
fn handle_frozen_counter(
&mut self,
info: HeaderInfo,
iter: &mut dyn Iterator<Item = (FrozenCounter, u16)>,
) {
println!("Frozen Counters:");
println!("Qualifier: {}", info.qualifier);
println!("Variation: {}", info.variation);
for (x, idx) in iter {
println!(
"Frozen Counter {}: Value={} Flags={:#04X} Time={:?}",
idx, x.value, x.flags.value, x.time
);
}
}
fn handle_analog_input(
&mut self,
info: HeaderInfo,
iter: &mut dyn Iterator<Item = (AnalogInput, u16)>,
) {
println!("Analog Inputs:");
println!("Qualifier: {}", info.qualifier);
println!("Variation: {}", info.variation);
for (x, idx) in iter {
println!(
"AI {}: Value={} Flags={:#04X} Time={:?}",
idx, x.value, x.flags.value, x.time
);
}
}
fn handle_analog_output_status(
&mut self,
info: HeaderInfo,
iter: &mut dyn Iterator<Item = (AnalogOutputStatus, u16)>,
) {
println!("Analog Output Statuses:");
println!("Qualifier: {}", info.qualifier);
println!("Variation: {}", info.variation);
for (x, idx) in iter {
println!(
"AOS {}: Value={} Flags={:#04X} Time={:?}",
idx, x.value, x.flags.value, x.time
);
}
}
fn handle_octet_string<'a>(
&mut self,
info: HeaderInfo,
iter: &mut dyn Iterator<Item = (&'a [u8], u16)>,
) {
println!("Octet Strings:");
println!("Qualifier: {}", info.qualifier);
println!("Variation: {}", info.variation);
for (x, idx) in iter {
println!("Octet String {}: Value={:X?}", idx, x);
}
}
}
void begin_fragment(dnp3_read_type_t read_type, dnp3_response_header_t header, void *arg)
{
printf("Beginning fragment (broadcast: %u)\n", header.iin.iin1.broadcast);
}
void end_fragment(dnp3_read_type_t read_type, dnp3_response_header_t header, void *arg) { printf("End fragment\n"); }
void handle_binary_input(dnp3_header_info_t info, dnp3_binary_input_iterator_t *it, void *arg)
{
printf("Binaries:\n");
printf("Qualifier: %s \n", dnp3_qualifier_code_to_string(info.qualifier));
printf("Variation: %s \n", dnp3_variation_to_string(info.variation));
dnp3_binary_input_t *value = NULL;
while (value = dnp3_binary_input_iterator_next(it)) {
printf("BI %u: Value=%u Flags=0x%02X Time=%" PRIu64 "\n", value->index, value->value, value->flags.value, value->time.value);
}
}
void handle_double_bit_binary_input(dnp3_header_info_t info, dnp3_double_bit_binary_input_iterator_t *it, void *arg)
{
printf("Double Bit Binaries:\n");
printf("Qualifier: %s \n", dnp3_qualifier_code_to_string(info.qualifier));
printf("Variation: %s \n", dnp3_variation_to_string(info.variation));
dnp3_double_bit_binary_input_t *value = NULL;
while (value = dnp3_double_bit_binary_input_iterator_next(it)) {
printf("DBBI %u: Value=%X Flags=0x%02X Time=%" PRIu64 "\n", value->index, value->value, value->flags.value, value->time.value);
}
}
void handle_binary_output_status(dnp3_header_info_t info, dnp3_binary_output_status_iterator_t *it, void *arg)
{
printf("Binary Output Statuses:\n");
printf("Qualifier: %s \n", dnp3_qualifier_code_to_string(info.qualifier));
printf("Variation: %s \n", dnp3_variation_to_string(info.variation));
dnp3_binary_output_status_t *value = NULL;
while (value = dnp3_binary_output_status_iterator_next(it)) {
printf("BOS %u: Value=%u Flags=0x%02X Time=%" PRIu64 "\n", value->index, value->value, value->flags.value, value->time.value);
}
}
void handle_counter(dnp3_header_info_t info, dnp3_counter_iterator_t *it, void *arg)
{
printf("Counters:\n");
printf("Qualifier: %s \n", dnp3_qualifier_code_to_string(info.qualifier));
printf("Variation: %s \n", dnp3_variation_to_string(info.variation));
dnp3_counter_t *value = NULL;
while (value = dnp3_counter_iterator_next(it)) {
printf("Counter %u: Value=%u Flags=0x%02X Time=%" PRIu64 "\n", value->index, value->value, value->flags.value, value->time.value);
}
}
void handle_frozen_counter(dnp3_header_info_t info, dnp3_frozen_counter_iterator_t *it, void *arg)
{
printf("Frozen Counters:\n");
printf("Qualifier: %s \n", dnp3_qualifier_code_to_string(info.qualifier));
printf("Variation: %s \n", dnp3_variation_to_string(info.variation));
dnp3_frozen_counter_t *value = NULL;
while (value = dnp3_frozen_counter_iterator_next(it)) {
printf("Frozen Counter %u: Value=%u Flags=0x%02X Time=%" PRIu64 "\n", value->index, value->value, value->flags.value, value->time.value);
}
}
void handle_analog_input(dnp3_header_info_t info, dnp3_analog_input_iterator_t *it, void *arg)
{
printf("Analogs:\n");
printf("Qualifier: %s \n", dnp3_qualifier_code_to_string(info.qualifier));
printf("Variation: %s \n", dnp3_variation_to_string(info.variation));
dnp3_analog_input_t *value = NULL;
while (value = dnp3_analog_input_iterator_next(it)) {
printf("AI %u: Value=%f Flags=0x%02X Time=%" PRIu64 "\n", value->index, value->value, value->flags.value, value->time.value);
}
}
void handle_analog_output_status(dnp3_header_info_t info, dnp3_analog_output_status_iterator_t *it, void *arg)
{
printf("Analog Output Statuses:\n");
printf("Qualifier: %s \n", dnp3_qualifier_code_to_string(info.qualifier));
printf("Variation: %s \n", dnp3_variation_to_string(info.variation));
dnp3_analog_output_status_t *value = NULL;
while (value = dnp3_analog_output_status_iterator_next(it)) {
printf("AOS %u: Value=%f Flags=0x%02X Time=%" PRIu64 "\n", value->index, value->value, value->flags.value, value->time.value);
}
}
void handle_octet_strings(dnp3_header_info_t info, dnp3_octet_string_iterator_t *it, void *arg)
{
printf("Octet Strings:\n");
printf("Qualifier: %s \n", dnp3_qualifier_code_to_string(info.qualifier));
printf("Variation: %s \n", dnp3_variation_to_string(info.variation));
dnp3_octet_string_t *value = NULL;
while (value = dnp3_octet_string_iterator_next(it)) {
printf("Octet String: %u: Value=", value->index);
uint8_t *byte = dnp3_byte_iterator_next(value->value);
while (byte != NULL) {
printf("%02X", *byte);
byte = dnp3_byte_iterator_next(value->value);
}
printf("\n");
}
}
class ReadHandler : public dnp3::ReadHandler {
void begin_fragment(dnp3::ReadType read_type, const dnp3::ResponseHeader& header) override
{
std::cout << "Begin fragment (broadcast: " << header.iin.iin1.broadcast << ")" << std::endl;
}
void end_fragment(dnp3::ReadType read_type, const dnp3::ResponseHeader& header) override
{
std::cout << "End fragment" << std::endl;
}
void handle_binary_input(const dnp3::HeaderInfo &info, dnp3::BinaryInputIterator &it) override
{
while (it.next()) {
const auto value = it.get();
std::cout << "BinaryInput(" << value.index << "): value: " << value.value << " flags: " << value.flags << " time: " << value.time.value << std::endl;
}
}
void handle_double_bit_binary_input(const dnp3::HeaderInfo &info, dnp3::DoubleBitBinaryInputIterator &it) override
{
while (it.next()) {
const auto value = it.get();
std::cout << "DoubleBitBinaryInput(" << value.index << "): value: " << dnp3::to_string(value.value) << " flags: " << value.flags << " time: " << value.time.value << std::endl;
}
}
void handle_binary_output_status(const dnp3::HeaderInfo& info, dnp3::BinaryOutputStatusIterator& it) override {
while (it.next()) {
const auto value = it.get();
std::cout << "BinaryOutputStatus(" << value.index << "): value: " << value.value << " flags: " << value.flags << " time: " << value.time.value << std::endl;
}
}
void handle_counter(const dnp3::HeaderInfo& info, dnp3::CounterIterator& it) override {
while (it.next()) {
const auto value = it.get();
std::cout << "Counter(" << value.index << "): value: " << value.value << " flags: " << value.flags << " time: " << value.time.value << std::endl;
}
}
void handle_frozen_counter(const dnp3::HeaderInfo& info, dnp3::FrozenCounterIterator& it) override {
while (it.next()) {
const auto value = it.get();
std::cout << "FrozenCounter(" << value.index << "): value: " << value.value << " flags: " << value.flags << " time: " << value.time.value << std::endl;
}
}
void handle_analog_input(const dnp3::HeaderInfo &info, dnp3::AnalogInputIterator &it) override
{
while (it.next()) {
const auto value = it.get();
std::cout << "AnalogInput(" << value.index << "): value: " << value.value << " flags: " << value.flags << " time: " << value.time.value << std::endl;
}
}
void handle_analog_output_status(const dnp3::HeaderInfo& info, dnp3::AnalogOutputStatusIterator& it) override {
while (it.next()) {
const auto value = it.get();
std::cout << "AnalogOutputStatus(" << value.index << "): value: " << value.value << " flags: " << value.flags << " time: " << value.time.value << std::endl;
}
}
void handle_octet_string(const dnp3::HeaderInfo& info, dnp3::OctetStringIterator& it) override {
while (it.next()) {
auto value = it.get();
std::cout << "OctetString(" << value.index << "): value: [";
bool first = false;
while (value.value.next()) {
const auto byte = value.value.get();
if (!first) {
std::cout << ",";
}
write_hex_byte(std::cout, byte);
first = false;
}
std::cout << "]" << std::endl;
}
}
};
class TestReadHandler implements ReadHandler {
@Override
public void beginFragment(ReadType readType, ResponseHeader header) {
System.out.println("Beginning fragment (broadcast: " + header.iin.iin1.broadcast + ")");
}
@Override
public void endFragment(ReadType readType, ResponseHeader header) {
System.out.println("End fragment");
}
@Override
public void handleBinaryInput(HeaderInfo info, List<BinaryInput> it) {
System.out.println("Binary Inputs:");
System.out.println("Qualifier: " + info.qualifier);
System.out.println("Variation: " + info.variation);
it.forEach(
val -> {
System.out.println(
"BI "
+ val.index
+ ": Value="
+ val.value
+ " Flags="
+ val.flags.value
+ " Time="
+ val.time.value
+ " ("
+ val.time.quality
+ ")");
});
}
@Override
public void handleDoubleBitBinaryInput(HeaderInfo info, List<DoubleBitBinaryInput> it) {
System.out.println("Double Bit Binary Inputs:");
System.out.println("Qualifier: " + info.qualifier);
System.out.println("Variation: " + info.variation);
it.forEach(
val -> {
System.out.println(
"DBBI "
+ val.index
+ ": Value="
+ val.value
+ " Flags="
+ val.flags.value
+ " Time="
+ val.time.value
+ " ("
+ val.time.quality
+ ")");
});
}
@Override
public void handleBinaryOutputStatus(HeaderInfo info, List<BinaryOutputStatus> it) {
System.out.println("Binary Output Statuses:");
System.out.println("Qualifier: " + info.qualifier);
System.out.println("Variation: " + info.variation);
it.forEach(
val -> {
System.out.println(
"BOS "
+ val.index
+ ": Value="
+ val.value
+ " Flags="
+ val.flags.value
+ " Time="
+ val.time.value
+ " ("
+ val.time.quality
+ ")");
});
}
@Override
public void handleCounter(HeaderInfo info, List<Counter> it) {
System.out.println("Counters:");
System.out.println("Qualifier: " + info.qualifier);
System.out.println("Variation: " + info.variation);
it.forEach(
val -> {
System.out.println(
"Counter "
+ val.index
+ ": Value="
+ val.value
+ " Flags="
+ val.flags.value
+ " Time="
+ val.time.value
+ " ("
+ val.time.quality
+ ")");
});
}
@Override
public void handleFrozenCounter(HeaderInfo info, List<FrozenCounter> it) {
System.out.println("Frozen Counters:");
System.out.println("Qualifier: " + info.qualifier);
System.out.println("Variation: " + info.variation);
it.forEach(
val -> {
System.out.println(
"Frozen Counter "
+ val.index
+ ": Value="
+ val.value
+ " Flags="
+ val.flags.value
+ " Time="
+ val.time.value
+ " ("
+ val.time.quality
+ ")");
});
}
@Override
public void handleAnalogInput(HeaderInfo info, List<AnalogInput> it) {
System.out.println("Analog Inputs:");
System.out.println("Qualifier: " + info.qualifier);
System.out.println("Variation: " + info.variation);
it.forEach(
val -> {
System.out.println(
"AI "
+ val.index
+ ": Value="
+ val.value
+ " Flags="
+ val.flags.value
+ " Time="
+ val.time.value
+ " ("
+ val.time.quality
+ ")");
});
}
@Override
public void handleAnalogOutputStatus(HeaderInfo info, List<AnalogOutputStatus> it) {
System.out.println("Analog Output Statuses:");
System.out.println("Qualifier: " + info.qualifier);
System.out.println("Variation: " + info.variation);
it.forEach(
val -> {
System.out.println(
"AOS "
+ val.index
+ ": Value="
+ val.value
+ " Flags="
+ val.flags.value
+ " Time="
+ val.time.value
+ " ("
+ val.time.quality
+ ")");
});
}
@Override
public void handleOctetString(HeaderInfo info, List<OctetString> it) {
System.out.println("Octet Strings:");
System.out.println("Qualifier: " + info.qualifier);
System.out.println("Variation: " + info.variation);
it.forEach(
val -> {
System.out.print("Octet String " + val.index + ": Value=");
val.value.forEach(
b -> System.out.print(String.format("%02X", b.byteValue()) + " "));
System.out.println();
});
}
}
class TestReadHandler : IReadHandler
{
public void BeginFragment(ReadType readType, ResponseHeader header)
{
Console.WriteLine($"Beginning fragment (broadcast: {header.Iin.Iin1.Broadcast})");
}
public void EndFragment(ReadType readType, ResponseHeader header)
{
Console.WriteLine("End fragment");
}
public void HandleBinaryInput(HeaderInfo info, ICollection<BinaryInput> values)
{
Console.WriteLine("Binary Inputs:");
Console.WriteLine("Qualifier: " + info.Qualifier);
Console.WriteLine("Variation: " + info.Variation);
foreach (var val in values)
{
Console.WriteLine($"BI {val.Index}: Value={val.Value} Flags={val.Flags.Value} Time={val.Time.Value} ({val.Time.Quality})");
}
}
public void HandleDoubleBitBinaryInput(HeaderInfo info, ICollection<DoubleBitBinaryInput> values)
{
Console.WriteLine("Double Bit Binary Inputs:");
Console.WriteLine("Qualifier: " + info.Qualifier);
Console.WriteLine("Variation: " + info.Variation);
foreach (var val in values)
{
Console.WriteLine($"DBBI {val.Index}: Value={val.Value} Flags={val.Flags.Value} Time={val.Time.Value} ({val.Time.Quality})");
}
}
public void HandleBinaryOutputStatus(HeaderInfo info, ICollection<BinaryOutputStatus> values)
{
Console.WriteLine("Binary Output Statuses:");
Console.WriteLine("Qualifier: " + info.Qualifier);
Console.WriteLine("Variation: " + info.Variation);
foreach (var val in values)
{
Console.WriteLine($"BOS {val.Index}: Value={val.Value} Flags={val.Flags.Value} Time={val.Time.Value} ({val.Time.Quality})");
}
}
public void HandleCounter(HeaderInfo info, ICollection<Counter> values)
{
Console.WriteLine("Counters:");
Console.WriteLine("Qualifier: " + info.Qualifier);
Console.WriteLine("Variation: " + info.Variation);
foreach (var val in values)
{
Console.WriteLine($"Counter {val.Index}: Value={val.Value} Flags={val.Flags.Value} Time={val.Time.Value} ({val.Time.Quality})");
}
}
public void HandleFrozenCounter(HeaderInfo info, ICollection<FrozenCounter> values)
{
Console.WriteLine("Frozen Counters:");
Console.WriteLine("Qualifier: " + info.Qualifier);
Console.WriteLine("Variation: " + info.Variation);
foreach (var val in values)
{
Console.WriteLine($"Frozen Counter {val.Index}: Value={val.Value} Flags={val.Flags.Value} Time={val.Time.Value} ({val.Time.Quality})");
}
}
public void HandleAnalogInput(HeaderInfo info, ICollection<AnalogInput> values)
{
Console.WriteLine("Analog Inputs:");
Console.WriteLine("Qualifier: " + info.Qualifier);
Console.WriteLine("Variation: " + info.Variation);
foreach (var val in values)
{
Console.WriteLine($"AI {val.Index}: Value={val.Value} Flags={val.Flags.Value} Time={val.Time.Value} ({val.Time.Quality})");
}
}
public void HandleAnalogOutputStatus(HeaderInfo info, ICollection<AnalogOutputStatus> values)
{
Console.WriteLine("Analog Output Statuses:");
Console.WriteLine("Qualifier: " + info.Qualifier);
Console.WriteLine("Variation: " + info.Variation);
foreach (var val in values)
{
Console.WriteLine($"AOS {val.Index}: Value={val.Value} Flags={val.Flags.Value} Time={val.Time.Value} ({val.Time.Quality})");
}
}
public void HandleOctetString(HeaderInfo info, ICollection<OctetString> values)
{
Console.WriteLine("Octet Strings:");
Console.WriteLine("Qualifier: " + info.Qualifier);
Console.WriteLine("Variation: " + info.Variation);
foreach (var val in values)
{
Console.Write($"Octet String {val.Index}: Value=");
foreach (var b in val.Value)
{
Console.Write($"{b:X2} ");
}
Console.WriteLine();
}
}
}