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 type | Values from |
|---|---|
Unsolicited | Unsolicited responses |
StartupIntegrity | The integrity scans the master runs on its own: at startup, after the outstation restarts, and after an event buffer overflow |
PeriodicPoll | Your polls, and the automatic event scans |
SinglePoll | One-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:
ReadHandler::begin_fragmentReadHandler::handle_<TYPE>for each object header in the messageReadHandler::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.
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
- Rust
- C
- C++
- Java
- C#
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}");
}
// print helpers, defined after the callbacks
void print_header(const char *name, dnp3_header_info_t info);
void print_flags_and_time(dnp3_flags_t flags, dnp3_timestamp_t time);
void print_time(dnp3_timestamp_t time);
void print_bytes(dnp3_byte_iterator_t *bytes);
void begin_fragment(dnp3_read_type_t read_type, dnp3_response_header_t header, void *context)
{
printf("begin fragment: %s (broadcast: %s)\n", dnp3_read_type_to_string(read_type), header.iin.iin1.broadcast ? "true" : "false");
}
void end_fragment(dnp3_read_type_t read_type, dnp3_response_header_t header, void *context) { printf("end fragment\n"); }
void handle_binary_input(dnp3_header_info_t info, dnp3_binary_input_iterator_t *it, void *context)
{
print_header("Binary inputs", info);
dnp3_binary_input_t *value = NULL;
while ((value = dnp3_binary_input_iterator_next(it))) {
printf("BI %u: value=%s", value->index, value->value ? "true" : "false");
print_flags_and_time(value->flags, value->time);
}
}
void handle_double_bit_binary_input(dnp3_header_info_t info, dnp3_double_bit_binary_input_iterator_t *it, void *context)
{
print_header("Double-bit binary inputs", info);
dnp3_double_bit_binary_input_t *value = NULL;
while ((value = dnp3_double_bit_binary_input_iterator_next(it))) {
printf("DBBI %u: value=%s", value->index, dnp3_double_bit_to_string(value->value));
print_flags_and_time(value->flags, value->time);
}
}
void handle_binary_output_status(dnp3_header_info_t info, dnp3_binary_output_status_iterator_t *it, void *context)
{
print_header("Binary output statuses", info);
dnp3_binary_output_status_t *value = NULL;
while ((value = dnp3_binary_output_status_iterator_next(it))) {
printf("BOS %u: value=%s", value->index, value->value ? "true" : "false");
print_flags_and_time(value->flags, value->time);
}
}
void handle_counter(dnp3_header_info_t info, dnp3_counter_iterator_t *it, void *context)
{
print_header("Counters", info);
dnp3_counter_t *value = NULL;
while ((value = dnp3_counter_iterator_next(it))) {
printf("C %u: value=%u", value->index, value->value);
print_flags_and_time(value->flags, value->time);
}
}
void handle_frozen_counter(dnp3_header_info_t info, dnp3_frozen_counter_iterator_t *it, void *context)
{
print_header("Frozen counters", info);
dnp3_frozen_counter_t *value = NULL;
while ((value = dnp3_frozen_counter_iterator_next(it))) {
printf("FC %u: value=%u", value->index, value->value);
print_flags_and_time(value->flags, value->time);
}
}
void handle_analog_input(dnp3_header_info_t info, dnp3_analog_input_iterator_t *it, void *context)
{
print_header("Analog inputs", info);
dnp3_analog_input_t *value = NULL;
while ((value = dnp3_analog_input_iterator_next(it))) {
printf("AI %u: value=%.15g", value->index, value->value);
print_flags_and_time(value->flags, value->time);
}
}
void handle_frozen_analog_input(dnp3_header_info_t info, dnp3_frozen_analog_input_iterator_t *it, void *context)
{
print_header("Frozen analog inputs", info);
dnp3_frozen_analog_input_t *value = NULL;
while ((value = dnp3_frozen_analog_input_iterator_next(it))) {
printf("FAI %u: value=%.15g", value->index, value->value);
print_flags_and_time(value->flags, value->time);
}
}
void handle_analog_input_dead_band(dnp3_header_info_t info, dnp3_analog_input_dead_band_iterator_t *it, void *context)
{
print_header("Analog input deadbands", info);
dnp3_analog_input_dead_band_t *value = NULL;
while ((value = dnp3_analog_input_dead_band_iterator_next(it))) {
printf("AIDB %u: value=%.15g\n", value->index, value->value);
}
}
void handle_analog_output_status(dnp3_header_info_t info, dnp3_analog_output_status_iterator_t *it, void *context)
{
print_header("Analog output statuses", info);
dnp3_analog_output_status_t *value = NULL;
while ((value = dnp3_analog_output_status_iterator_next(it))) {
printf("AOS %u: value=%.15g", value->index, value->value);
print_flags_and_time(value->flags, value->time);
}
}
void handle_binary_output_command_event(dnp3_header_info_t info, dnp3_binary_output_command_event_iterator_t *it, void *context)
{
print_header("Binary output command events", info);
dnp3_binary_output_command_event_t *value = NULL;
while ((value = dnp3_binary_output_command_event_iterator_next(it))) {
printf("BOCE %u: commanded state=%s status=%s time=", value->index, value->commanded_state ? "true" : "false",
dnp3_command_status_to_string(value->status));
print_time(value->time);
printf("\n");
}
}
void handle_analog_output_command_event(dnp3_header_info_t info, dnp3_analog_output_command_event_iterator_t *it, void *context)
{
print_header("Analog output command events", info);
dnp3_analog_output_command_event_t *value = NULL;
while ((value = dnp3_analog_output_command_event_iterator_next(it))) {
printf("AOCE %u: commanded value=%.15g status=%s time=", value->index, value->commanded_value, dnp3_command_status_to_string(value->status));
print_time(value->time);
printf("\n");
}
}
void handle_unsigned_integer(dnp3_header_info_t info, dnp3_unsigned_integer_iterator_t *it, void *context)
{
print_header("Unsigned integers", info);
dnp3_unsigned_integer_t *value = NULL;
while ((value = dnp3_unsigned_integer_iterator_next(it))) {
printf("UI %u: value=%u\n", value->index, value->value);
}
}
void handle_octet_string(dnp3_header_info_t info, dnp3_octet_string_iterator_t *it, void *context)
{
print_header("Octet strings", info);
dnp3_octet_string_t *value = NULL;
while ((value = dnp3_octet_string_iterator_next(it))) {
printf("OS %u: value=", value->index);
print_bytes(value->value);
printf("\n");
}
}
void handle_abs_time(dnp3_header_info_t info, dnp3_timestamp_t time, void *context)
{
print_header("Absolute time", info);
printf("time=%" PRIu64 "\n", time.value);
}
void handle_string_attr(dnp3_header_info_t info, dnp3_string_attr_t attr, uint8_t set, uint8_t variation, const char *value, void *context)
{
print_header("Device attributes", info);
printf("attribute set=%u variation=%u value=%s\n", set, variation, value);
}
void handle_variation_list_attr(dnp3_header_info_t info, dnp3_variation_list_attr_t attr, uint8_t set, uint8_t variation,
dnp3_attr_item_iter_t *items, void *context)
{
print_header("Device attributes", info);
printf("attribute set=%u variation=%u value=[", set, variation);
dnp3_attr_item_t *item = dnp3_attr_item_iter_next(items);
while (item) {
printf("%u%s", item->variation, item->properties.is_writable ? " (writable)" : "");
item = dnp3_attr_item_iter_next(items);
printf("%s", item ? ", " : "");
}
printf("]\n");
}
void handle_uint_attr(dnp3_header_info_t info, dnp3_uint_attr_t attr, uint8_t set, uint8_t variation, uint32_t value, void *context)
{
print_header("Device attributes", info);
printf("attribute set=%u variation=%u value=%u\n", set, variation, value);
}
void handle_bool_attr(dnp3_header_info_t info, dnp3_bool_attr_t attr, uint8_t set, uint8_t variation, bool value, void *context)
{
print_header("Device attributes", info);
printf("attribute set=%u variation=%u value=%s\n", set, variation, value ? "true" : "false");
}
void handle_int_attr(dnp3_header_info_t info, dnp3_int_attr_t attr, uint8_t set, uint8_t variation, int32_t value, void *context)
{
print_header("Device attributes", info);
printf("attribute set=%u variation=%u value=%d\n", set, variation, value);
}
void handle_time_attr(dnp3_header_info_t info, dnp3_time_attr_t attr, uint8_t set, uint8_t variation, uint64_t value, void *context)
{
print_header("Device attributes", info);
printf("attribute set=%u variation=%u value=%" PRIu64 "\n", set, variation, value);
}
void handle_float_attr(dnp3_header_info_t info, dnp3_float_attr_t attr, uint8_t set, uint8_t variation, double value, void *context)
{
print_header("Device attributes", info);
printf("attribute set=%u variation=%u value=%.15g\n", set, variation, value);
}
void handle_octet_string_attr(dnp3_header_info_t info, dnp3_octet_string_attr_t attr, uint8_t set, uint8_t variation, dnp3_byte_iterator_t *value,
void *context)
{
print_header("Device attributes", info);
printf("attribute set=%u variation=%u value=", set, variation);
print_bytes(value);
printf("\n");
}
void handle_bit_string_attr(dnp3_header_info_t info, dnp3_bit_string_attr_t attr, uint8_t set, uint8_t variation, dnp3_byte_iterator_t *value,
void *context)
{
print_header("Device attributes", info);
printf("attribute set=%u variation=%u value=", set, variation);
print_bytes(value);
printf("\n");
}
dnp3_read_handler_t get_read_handler(void)
{
return (dnp3_read_handler_t){
.begin_fragment = &begin_fragment,
.end_fragment = &end_fragment,
.handle_binary_input = &handle_binary_input,
.handle_double_bit_binary_input = &handle_double_bit_binary_input,
.handle_binary_output_status = &handle_binary_output_status,
.handle_counter = &handle_counter,
.handle_frozen_counter = &handle_frozen_counter,
.handle_analog_input = &handle_analog_input,
.handle_frozen_analog_input = &handle_frozen_analog_input,
.handle_analog_output_status = &handle_analog_output_status,
.handle_binary_output_command_event = &handle_binary_output_command_event,
.handle_analog_output_command_event = &handle_analog_output_command_event,
.handle_unsigned_integer = &handle_unsigned_integer,
.handle_analog_input_dead_band = &handle_analog_input_dead_band,
.handle_octet_string = &handle_octet_string,
.handle_abs_time = &handle_abs_time,
.handle_string_attr = &handle_string_attr,
.handle_variation_list_attr = &handle_variation_list_attr,
.handle_uint_attr = &handle_uint_attr,
.handle_bool_attr = &handle_bool_attr,
.handle_int_attr = &handle_int_attr,
.handle_time_attr = &handle_time_attr,
.handle_float_attr = &handle_float_attr,
.handle_octet_string_attr = &handle_octet_string_attr,
.handle_bit_string_attr = &handle_bit_string_attr,
.on_destroy = NULL,
.ctx = NULL,
};
}
void print_header(const char *name, dnp3_header_info_t info)
{
printf("%s (%s, %s)\n", name, dnp3_qualifier_code_to_string(info.qualifier), dnp3_variation_to_string(info.variation));
}
// ends the line of a measurement with its flags and time
void print_flags_and_time(dnp3_flags_t flags, dnp3_timestamp_t time)
{
printf(" flags=0x%02X time=", flags.value);
print_time(time);
printf("\n");
}
void print_time(dnp3_timestamp_t time)
{
if (time.quality == DNP3_TIME_QUALITY_SYNCHRONIZED_TIME) {
printf("%" PRIu64 " (synchronized)", time.value);
}
else if (time.quality == DNP3_TIME_QUALITY_UNSYNCHRONIZED_TIME) {
printf("%" PRIu64 " (unsynchronized)", time.value);
}
else {
printf("none");
}
}
// prints the bytes as a list of hex values
void print_bytes(dnp3_byte_iterator_t *bytes)
{
printf("[");
uint8_t *byte = dnp3_byte_iterator_next(bytes);
while (byte) {
printf("%02X", *byte);
byte = dnp3_byte_iterator_next(bytes);
printf("%s", byte ? ", " : "");
}
printf("]");
}
// print helpers, defined after the handler
void print_header(const char *name, const dnp3::HeaderInfo &info);
void print_flags_and_time(const dnp3::Flags &flags, const dnp3::Timestamp &time);
void print_time(const dnp3::Timestamp &time);
void print_bytes(dnp3::ByteIterator &bytes);
void print_attribute_start(const dnp3::HeaderInfo &info, uint8_t set, uint8_t variation);
class ReadHandler : public dnp3::ReadHandler {
void begin_fragment(dnp3::ReadType read_type, const dnp3::ResponseHeader &header) override
{
std::cout << "begin fragment: " << dnp3::to_string(read_type) << " (broadcast: " << (header.iin.iin1.broadcast ? "true" : "false") << ")"
<< 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
{
print_header("Binary inputs", info);
while (it.next()) {
const auto value = it.get();
std::cout << "BI " << value.index << ": value=" << (value.value ? "true" : "false");
print_flags_and_time(value.flags, value.time);
}
}
void handle_double_bit_binary_input(const dnp3::HeaderInfo &info, dnp3::DoubleBitBinaryInputIterator &it) override
{
print_header("Double-bit binary inputs", info);
while (it.next()) {
const auto value = it.get();
std::cout << "DBBI " << value.index << ": value=" << dnp3::to_string(value.value);
print_flags_and_time(value.flags, value.time);
}
}
void handle_binary_output_status(const dnp3::HeaderInfo &info, dnp3::BinaryOutputStatusIterator &it) override
{
print_header("Binary output statuses", info);
while (it.next()) {
const auto value = it.get();
std::cout << "BOS " << value.index << ": value=" << (value.value ? "true" : "false");
print_flags_and_time(value.flags, value.time);
}
}
void handle_counter(const dnp3::HeaderInfo &info, dnp3::CounterIterator &it) override
{
print_header("Counters", info);
while (it.next()) {
const auto value = it.get();
std::cout << "C " << value.index << ": value=" << value.value;
print_flags_and_time(value.flags, value.time);
}
}
void handle_frozen_counter(const dnp3::HeaderInfo &info, dnp3::FrozenCounterIterator &it) override
{
print_header("Frozen counters", info);
while (it.next()) {
const auto value = it.get();
std::cout << "FC " << value.index << ": value=" << value.value;
print_flags_and_time(value.flags, value.time);
}
}
void handle_analog_input(const dnp3::HeaderInfo &info, dnp3::AnalogInputIterator &it) override
{
print_header("Analog inputs", info);
while (it.next()) {
const auto value = it.get();
std::cout << "AI " << value.index << ": value=" << std::setprecision(15) << value.value;
print_flags_and_time(value.flags, value.time);
}
}
void handle_frozen_analog_input(const dnp3::HeaderInfo &info, dnp3::FrozenAnalogInputIterator &it) override
{
print_header("Frozen analog inputs", info);
while (it.next()) {
const auto value = it.get();
std::cout << "FAI " << value.index << ": value=" << std::setprecision(15) << value.value;
print_flags_and_time(value.flags, value.time);
}
}
void handle_analog_input_dead_band(const dnp3::HeaderInfo &info, dnp3::AnalogInputDeadBandIterator &it) override
{
print_header("Analog input deadbands", info);
while (it.next()) {
const auto value = it.get();
std::cout << "AIDB " << value.index << ": value=" << std::setprecision(15) << value.value << std::endl;
}
}
void handle_analog_output_status(const dnp3::HeaderInfo &info, dnp3::AnalogOutputStatusIterator &it) override
{
print_header("Analog output statuses", info);
while (it.next()) {
const auto value = it.get();
std::cout << "AOS " << value.index << ": value=" << std::setprecision(15) << value.value;
print_flags_and_time(value.flags, value.time);
}
}
void handle_binary_output_command_event(const dnp3::HeaderInfo &info, dnp3::BinaryOutputCommandEventIterator &it) override
{
print_header("Binary output command events", info);
while (it.next()) {
const auto value = it.get();
std::cout << "BOCE " << value.index << ": commanded state=" << (value.commanded_state ? "true" : "false")
<< " status=" << dnp3::to_string(value.status) << " time=";
print_time(value.time);
std::cout << std::endl;
}
}
void handle_analog_output_command_event(const dnp3::HeaderInfo &info, dnp3::AnalogOutputCommandEventIterator &it) override
{
print_header("Analog output command events", info);
while (it.next()) {
const auto value = it.get();
std::cout << "AOCE " << value.index << ": commanded value=" << std::setprecision(15) << value.commanded_value << " status=" << dnp3::to_string(value.status)
<< " time=";
print_time(value.time);
std::cout << std::endl;
}
}
void handle_unsigned_integer(const dnp3::HeaderInfo &info, dnp3::UnsignedIntegerIterator &it) override
{
print_header("Unsigned integers", info);
while (it.next()) {
const auto value = it.get();
std::cout << "UI " << value.index << ": value=" << static_cast<unsigned>(value.value) << std::endl;
}
}
void handle_octet_string(const dnp3::HeaderInfo &info, dnp3::OctetStringIterator &it) override
{
print_header("Octet strings", info);
while (it.next()) {
auto value = it.get();
std::cout << "OS " << value.index << ": value=";
print_bytes(value.value);
std::cout << std::endl;
}
}
void handle_abs_time(const dnp3::HeaderInfo &info, const dnp3::Timestamp &time) override
{
print_header("Absolute time", info);
std::cout << "time=" << time.value << std::endl;
}
void handle_string_attr(const dnp3::HeaderInfo &info, dnp3::StringAttr attr, uint8_t set, uint8_t variation, const char *value) override
{
print_attribute_start(info, set, variation);
std::cout << value << std::endl;
}
void handle_variation_list_attr(const dnp3::HeaderInfo &info, dnp3::VariationListAttr attr, uint8_t set, uint8_t variation,
dnp3::AttrItemIter &items) override
{
print_attribute_start(info, set, variation);
std::cout << "[";
bool first = true;
while (items.next()) {
const auto item = items.get();
std::cout << (first ? "" : ", ") << static_cast<unsigned>(item.variation)
<< (item.properties.is_writable ? " (writable)" : "");
first = false;
}
std::cout << "]" << std::endl;
}
void handle_uint_attr(const dnp3::HeaderInfo &info, dnp3::UintAttr attr, uint8_t set, uint8_t variation, uint32_t value) override
{
print_attribute_start(info, set, variation);
std::cout << value << std::endl;
}
void handle_bool_attr(const dnp3::HeaderInfo &info, dnp3::BoolAttr attr, uint8_t set, uint8_t variation, bool value) override
{
print_attribute_start(info, set, variation);
std::cout << (value ? "true" : "false") << std::endl;
}
void handle_int_attr(const dnp3::HeaderInfo &info, dnp3::IntAttr attr, uint8_t set, uint8_t variation, int32_t value) override
{
print_attribute_start(info, set, variation);
std::cout << value << std::endl;
}
void handle_time_attr(const dnp3::HeaderInfo &info, dnp3::TimeAttr attr, uint8_t set, uint8_t variation, uint64_t value) override
{
print_attribute_start(info, set, variation);
std::cout << value << std::endl;
}
void handle_float_attr(const dnp3::HeaderInfo &info, dnp3::FloatAttr attr, uint8_t set, uint8_t variation, double value) override
{
print_attribute_start(info, set, variation);
std::cout << std::setprecision(15) << value << std::endl;
}
void handle_octet_string_attr(const dnp3::HeaderInfo &info, dnp3::OctetStringAttr attr, uint8_t set, uint8_t variation,
dnp3::ByteIterator &value) override
{
print_attribute_start(info, set, variation);
print_bytes(value);
std::cout << std::endl;
}
void handle_bit_string_attr(const dnp3::HeaderInfo &info, dnp3::BitStringAttr attr, uint8_t set, uint8_t variation,
dnp3::ByteIterator &value) override
{
print_attribute_start(info, set, variation);
print_bytes(value);
std::cout << std::endl;
}
};
std::unique_ptr<dnp3::ReadHandler> create_read_handler()
{
return std::make_unique<ReadHandler>();
}
void print_header(const char *name, const dnp3::HeaderInfo &info)
{
std::cout << name << " (" << dnp3::to_string(info.qualifier) << ", " << dnp3::to_string(info.variation) << ")" << std::endl;
}
// ends the line of a measurement with its flags and time
void print_flags_and_time(const dnp3::Flags &flags, const dnp3::Timestamp &time)
{
std::cout << " flags=0x" << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << static_cast<unsigned>(flags.value) << std::dec
<< std::nouppercase << std::setfill(' ') << " time=";
print_time(time);
std::cout << std::endl;
}
void print_time(const dnp3::Timestamp &time)
{
if (time.quality == dnp3::TimeQuality::synchronized_time) {
std::cout << time.value << " (synchronized)";
}
else if (time.quality == dnp3::TimeQuality::unsynchronized_time) {
std::cout << time.value << " (unsynchronized)";
}
else {
std::cout << "none";
}
}
// prints the bytes as a list of hex values
void print_bytes(dnp3::ByteIterator &bytes)
{
std::cout << "[" << std::hex << std::uppercase << std::setfill('0');
bool first = true;
while (bytes.next()) {
std::cout << (first ? "" : ", ") << std::setw(2) << static_cast<unsigned>(bytes.get());
first = false;
}
std::cout << std::dec << std::nouppercase << std::setfill(' ') << "]";
}
// prints the header and the start of an attribute line, up to its value
void print_attribute_start(const dnp3::HeaderInfo &info, uint8_t set, uint8_t variation)
{
print_header("Device attributes", info);
std::cout << "attribute set=" << static_cast<unsigned>(set) << " variation=" << static_cast<unsigned>(variation) << " value=";
}
class ExampleReadHandler implements ReadHandler {
@Override
public void beginFragment(ReadType readType, ResponseHeader header) {
System.out.println("begin fragment: " + readType + " (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> values) {
printHeader("Binary inputs", info);
values.forEach(x -> printPoint("BI", x.index.intValue(), x.value, x.flags, x.time));
}
@Override
public void handleDoubleBitBinaryInput(HeaderInfo info, List<DoubleBitBinaryInput> values) {
printHeader("Double-bit binary inputs", info);
values.forEach(x -> printPoint("DBBI", x.index.intValue(), x.value, x.flags, x.time));
}
@Override
public void handleBinaryOutputStatus(HeaderInfo info, List<BinaryOutputStatus> values) {
printHeader("Binary output statuses", info);
values.forEach(x -> printPoint("BOS", x.index.intValue(), x.value, x.flags, x.time));
}
@Override
public void handleCounter(HeaderInfo info, List<Counter> values) {
printHeader("Counters", info);
values.forEach(x -> printPoint("C", x.index.intValue(), x.value, x.flags, x.time));
}
@Override
public void handleFrozenCounter(HeaderInfo info, List<FrozenCounter> values) {
printHeader("Frozen counters", info);
values.forEach(x -> printPoint("FC", x.index.intValue(), x.value, x.flags, x.time));
}
@Override
public void handleAnalogInput(HeaderInfo info, List<AnalogInput> values) {
printHeader("Analog inputs", info);
values.forEach(x -> printPoint("AI", x.index.intValue(), x.value, x.flags, x.time));
}
@Override
public void handleFrozenAnalogInput(HeaderInfo info, List<FrozenAnalogInput> values) {
printHeader("Frozen analog inputs", info);
values.forEach(x -> printPoint("FAI", x.index.intValue(), x.value, x.flags, x.time));
}
@Override
public void handleAnalogInputDeadBand(HeaderInfo info, List<AnalogInputDeadBand> values) {
printHeader("Analog input deadbands", info);
values.forEach(x -> System.out.println("AIDB " + x.index + ": value=" + x.value));
}
@Override
public void handleAnalogOutputStatus(HeaderInfo info, List<AnalogOutputStatus> values) {
printHeader("Analog output statuses", info);
values.forEach(x -> printPoint("AOS", x.index.intValue(), x.value, x.flags, x.time));
}
@Override
public void handleBinaryOutputCommandEvent(HeaderInfo info, List<BinaryOutputCommandEvent> values) {
printHeader("Binary output command events", info);
values.forEach(x -> System.out.println(
"BOCE " + x.index + ": commanded state=" + x.commandedState + " status=" + x.status + " time=" + formatTime(x.time)));
}
@Override
public void handleAnalogOutputCommandEvent(HeaderInfo info, List<AnalogOutputCommandEvent> values) {
printHeader("Analog output command events", info);
values.forEach(x -> System.out.println(
"AOCE " + x.index + ": commanded value=" + x.commandedValue + " status=" + x.status + " time=" + formatTime(x.time)));
}
@Override
public void handleUnsignedInteger(HeaderInfo info, List<UnsignedInteger> values) {
printHeader("Unsigned integers", info);
values.forEach(x -> System.out.println("UI " + x.index + ": value=" + x.value));
}
@Override
public void handleOctetString(HeaderInfo info, List<OctetString> values) {
printHeader("Octet strings", info);
values.forEach(x -> System.out.println("OS " + x.index + ": value=" + formatBytes(x.value)));
}
@Override
public void handleStringAttr(HeaderInfo info, StringAttr attr, UByte set, UByte variation, String value) {
printHeader("Device attributes", info);
printAttribute(set, variation, value);
}
@Override
public void handleVariationListAttr(HeaderInfo info, VariationListAttr attr, UByte set, UByte variation, List<AttrItem> value) {
printHeader("Device attributes", info);
List<String> variations = new ArrayList<>();
value.forEach(item -> variations.add(item.variation + (item.properties.isWritable ? " (writable)" : "")));
printAttribute(set, variation, variations);
}
@Override
public void handleUintAttr(HeaderInfo info, UintAttr attr, UByte set, UByte variation, UInteger value) {
printHeader("Device attributes", info);
printAttribute(set, variation, value);
}
@Override
public void handleBoolAttr(HeaderInfo info, BoolAttr attr, UByte set, UByte variation, boolean value) {
printHeader("Device attributes", info);
printAttribute(set, variation, value);
}
@Override
public void handleIntAttr(HeaderInfo info, IntAttr attr, UByte set, UByte variation, int value) {
printHeader("Device attributes", info);
printAttribute(set, variation, value);
}
@Override
public void handleTimeAttr(HeaderInfo info, TimeAttr attr, UByte set, UByte variation, ULong value) {
printHeader("Device attributes", info);
printAttribute(set, variation, value);
}
@Override
public void handleFloatAttr(HeaderInfo info, FloatAttr attr, UByte set, UByte variation, double value) {
printHeader("Device attributes", info);
printAttribute(set, variation, value);
}
@Override
public void handleOctetStringAttr(HeaderInfo info, OctetStringAttr attr, UByte set, UByte variation, List<UByte> value) {
printHeader("Device attributes", info);
printAttribute(set, variation, formatBytes(value));
}
@Override
public void handleBitStringAttr(HeaderInfo info, BitStringAttr attr, UByte set, UByte variation, List<UByte> value) {
printHeader("Device attributes", info);
printAttribute(set, variation, formatBytes(value));
}
@Override
public void handleAbsTime(HeaderInfo info, Timestamp time) {
printHeader("Absolute time", info);
System.out.println("time=" + time.value);
}
private static void printHeader(String name, HeaderInfo info) {
System.out.println(name + " (" + info.qualifier + ", " + info.variation + ")");
}
private static void printPoint(String name, int index, Object value, Flags flags, Timestamp time) {
System.out.printf("%s %d: value=%s flags=0x%02X time=%s%n", name, index, value, flags.value.intValue(), formatTime(time));
}
private static String formatTime(Timestamp time) {
switch (time.quality) {
case SYNCHRONIZED_TIME:
return time.value + " (synchronized)";
case UNSYNCHRONIZED_TIME:
return time.value + " (unsynchronized)";
default:
return "none";
}
}
private static void printAttribute(UByte set, UByte variation, Object value) {
System.out.println("attribute set=" + set + " variation=" + variation + " value=" + value);
}
private static String formatBytes(List<UByte> bytes) {
List<String> hex = new ArrayList<>();
bytes.forEach(b -> hex.add(String.format("%02X", b.intValue())));
return hex.toString();
}
}
class ExampleReadHandler : IReadHandler
{
public void BeginFragment(ReadType readType, ResponseHeader header)
{
Console.WriteLine($"begin fragment: {readType} (broadcast: {FormatBool(header.Iin.Iin1.Broadcast)})");
}
public void EndFragment(ReadType readType, ResponseHeader header)
{
Console.WriteLine("end fragment");
}
public void HandleBinaryInput(HeaderInfo info, ICollection<BinaryInput> values)
{
PrintHeader("Binary inputs", info);
foreach (var x in values)
{
PrintPoint("BI", x.Index, FormatBool(x.Value), x.Flags, x.Time);
}
}
public void HandleDoubleBitBinaryInput(HeaderInfo info, ICollection<DoubleBitBinaryInput> values)
{
PrintHeader("Double-bit binary inputs", info);
foreach (var x in values)
{
PrintPoint("DBBI", x.Index, x.Value, x.Flags, x.Time);
}
}
public void HandleBinaryOutputStatus(HeaderInfo info, ICollection<BinaryOutputStatus> values)
{
PrintHeader("Binary output statuses", info);
foreach (var x in values)
{
PrintPoint("BOS", x.Index, FormatBool(x.Value), x.Flags, x.Time);
}
}
public void HandleCounter(HeaderInfo info, ICollection<Counter> values)
{
PrintHeader("Counters", info);
foreach (var x in values)
{
PrintPoint("C", x.Index, x.Value, x.Flags, x.Time);
}
}
public void HandleFrozenCounter(HeaderInfo info, ICollection<FrozenCounter> values)
{
PrintHeader("Frozen counters", info);
foreach (var x in values)
{
PrintPoint("FC", x.Index, x.Value, x.Flags, x.Time);
}
}
public void HandleAnalogInput(HeaderInfo info, ICollection<AnalogInput> values)
{
PrintHeader("Analog inputs", info);
foreach (var x in values)
{
PrintPoint("AI", x.Index, x.Value, x.Flags, x.Time);
}
}
public void HandleFrozenAnalogInput(HeaderInfo info, ICollection<FrozenAnalogInput> values)
{
PrintHeader("Frozen analog inputs", info);
foreach (var x in values)
{
PrintPoint("FAI", x.Index, x.Value, x.Flags, x.Time);
}
}
public void HandleAnalogInputDeadBand(HeaderInfo info, ICollection<AnalogInputDeadBand> values)
{
PrintHeader("Analog input deadbands", info);
foreach (var x in values)
{
Console.WriteLine($"AIDB {x.Index}: value={x.Value}");
}
}
public void HandleAnalogOutputStatus(HeaderInfo info, ICollection<AnalogOutputStatus> values)
{
PrintHeader("Analog output statuses", info);
foreach (var x in values)
{
PrintPoint("AOS", x.Index, x.Value, x.Flags, x.Time);
}
}
public void HandleBinaryOutputCommandEvent(HeaderInfo info, ICollection<BinaryOutputCommandEvent> values)
{
PrintHeader("Binary output command events", info);
foreach (var x in values)
{
Console.WriteLine($"BOCE {x.Index}: commanded state={FormatBool(x.CommandedState)} status={x.Status} time={FormatTime(x.Time)}");
}
}
public void HandleAnalogOutputCommandEvent(HeaderInfo info, ICollection<AnalogOutputCommandEvent> values)
{
PrintHeader("Analog output command events", info);
foreach (var x in values)
{
Console.WriteLine($"AOCE {x.Index}: commanded value={x.CommandedValue} status={x.Status} time={FormatTime(x.Time)}");
}
}
public void HandleUnsignedInteger(HeaderInfo info, ICollection<UnsignedInteger> values)
{
PrintHeader("Unsigned integers", info);
foreach (var x in values)
{
Console.WriteLine($"UI {x.Index}: value={x.Value}");
}
}
public void HandleOctetString(HeaderInfo info, ICollection<OctetString> values)
{
PrintHeader("Octet strings", info);
foreach (var x in values)
{
Console.WriteLine($"OS {x.Index}: value={FormatBytes(x.Value)}");
}
}
public void HandleStringAttr(HeaderInfo info, StringAttr attr, byte set, byte variation, string value)
{
PrintHeader("Device attributes", info);
PrintAttribute(set, variation, value);
}
public void HandleVariationListAttr(HeaderInfo info, VariationListAttr attr, byte set, byte variation, ICollection<AttrItem> value)
{
PrintHeader("Device attributes", info);
PrintAttribute(set, variation, $"[{string.Join(", ", value.Select(item => item.Variation + (item.Properties.IsWritable ? " (writable)" : "")))}]");
}
public void HandleUintAttr(HeaderInfo info, UintAttr attr, byte set, byte variation, uint value)
{
PrintHeader("Device attributes", info);
PrintAttribute(set, variation, value);
}
public void HandleBoolAttr(HeaderInfo info, BoolAttr attr, byte set, byte variation, bool value)
{
PrintHeader("Device attributes", info);
PrintAttribute(set, variation, FormatBool(value));
}
public void HandleIntAttr(HeaderInfo info, IntAttr attr, byte set, byte variation, int value)
{
PrintHeader("Device attributes", info);
PrintAttribute(set, variation, value);
}
public void HandleTimeAttr(HeaderInfo info, TimeAttr attr, byte set, byte variation, ulong value)
{
PrintHeader("Device attributes", info);
PrintAttribute(set, variation, value);
}
public void HandleFloatAttr(HeaderInfo info, FloatAttr attr, byte set, byte variation, double value)
{
PrintHeader("Device attributes", info);
PrintAttribute(set, variation, value);
}
public void HandleOctetStringAttr(HeaderInfo info, OctetStringAttr attr, byte set, byte variation, ICollection<byte> value)
{
PrintHeader("Device attributes", info);
PrintAttribute(set, variation, FormatBytes(value));
}
public void HandleBitStringAttr(HeaderInfo info, BitStringAttr attr, byte set, byte variation, ICollection<byte> value)
{
PrintHeader("Device attributes", info);
PrintAttribute(set, variation, FormatBytes(value));
}
public void HandleAbsTime(HeaderInfo info, Timestamp time)
{
PrintHeader("Absolute time", info);
Console.WriteLine($"time={time.Value}");
}
private static void PrintHeader(string name, HeaderInfo info)
{
Console.WriteLine($"{name} ({info.Qualifier}, {info.Variation})");
}
private static void PrintPoint(string name, ushort index, object value, Flags flags, Timestamp time)
{
Console.WriteLine($"{name} {index}: value={value} flags=0x{flags.Value:X2} time={FormatTime(time)}");
}
private static string FormatTime(Timestamp time)
{
switch (time.Quality)
{
case TimeQuality.SynchronizedTime:
return $"{time.Value} (synchronized)";
case TimeQuality.UnsynchronizedTime:
return $"{time.Value} (unsynchronized)";
default:
return "none";
}
}
private static void PrintAttribute(byte set, byte variation, object value)
{
Console.WriteLine($"attribute set={set} variation={variation} value={value}");
}
private static string FormatBytes(ICollection<byte> bytes)
{
return $"[{string.Join(", ", bytes.Select(b => b.ToString("X2")))}]";
}
private static string FormatBool(bool value)
{
return value ? "true" : "false";
}
}