Skip to main content

Database

Use the Database class to manage measurements the outstation exposes to the master. Despite the name, it's just an in-memory data structure protected by a mutex.

All database operations are executed inside a transaction protected by a mutex. Operations within a transaction are applied to the database and the event buffers atomically. If unsolicited responses are enabled, the outstation will automatically decide if an unsolicited response should be sent at the end of the transaction.

Access the database in a transaction two ways:

  1. Call Outstation.transaction to acquire a locked reference and make changes.

  2. ControlHandler callbacks and OutstationApplication freeze operations provide a DatabaseHandle with an identical transaction method.

tip

Structure any common update code to operate on the Database type and not depend on Outstation or DatabaseHandle.

Adding points​

Initialize points before the outstation exposes measurement data. This is typically done at creation, but you can add points to a running outstation. Each measurement type has unique configuration:

  • An optional event class assignment for the point, which a master can change at runtime (see saving what the master changes)
  • Default static and event variations for the type
  • Dead-bands for analog and counter types that default to zero (other types have no deadband)
caution

The default static and event variations for each point type may not do what you expect them to do. For example, some DNP3 event variations don't carry a timestamp. This can be confusing to new users who expect to see a timestamp value that changes as the outstation receives events.

See the documentation for each variation to understand its data.

A new point starts with a default value, such as 0 or false, and the RESTART flag. Update it after adding it if a connecting master shouldn't see RESTART.

Point indices don't need to be contiguous.

outstation.transaction(|db| {
// initialize POINT_COUNT points of each type
for i in 0..POINT_COUNT {
db.add(
i,
Some(EventClass::Class1),
// you can explicitly specify the configuration for each point ...
BinaryInputConfig {
s_var: StaticBinaryInputVariation::Group1Var1,
e_var: EventBinaryInputVariation::Group2Var2,
},
);
db.add(
i,
Some(EventClass::Class1),
// ... or just use the defaults
DoubleBitBinaryInputConfig::default(),
);
db.add(
i,
Some(EventClass::Class1),
BinaryOutputStatusConfig::default(),
);
db.add(i, Some(EventClass::Class1), CounterConfig::default());
db.add(i, Some(EventClass::Class1), FrozenCounterConfig::default());
db.add(
i,
Some(EventClass::Class1),
AnalogInputConfig {
s_var: StaticAnalogInputVariation::Group30Var1,
e_var: EventAnalogInputVariation::Group32Var1,
deadband: 0.0,
},
);
db.add(
i,
Some(EventClass::Class1),
AnalogOutputStatusConfig::default(),
);
db.add(i, Some(EventClass::Class1), OctetStringConfig);
}

// define device attributes made available to the master
let _ = db.define_attr(
AttrProp::default(),
StringAttr::DeviceManufacturersName.with_value("Step Function I/O"),
);
let _ = db.define_attr(
AttrProp::writable(),
StringAttr::UserAssignedLocation.with_value("Bend, OR"),
);
});

Updating points​

You can update a point value in a new transaction or in the same transaction you used to initialize it. This is useful if the outstation has local access to values at startup, such as via a local ADC. When initializing point values, it is recommended to use the UpdateOptions::no_event().

The Flags value can be built by ORing values from the constants available in Flag. Note that not all Flag values are available in every point type.

The UpdateOptions struct lets you precisely control how a point update is processed. Use the default constructor to:

  • Update the static value
  • Produce an event if the point value changes in a way that exceeds the deadband or if the flags change
tip

Use the UpdateOptions to ignore event creation during startup initialization if you don't want to create events for the initial values.

Getting point values​

Some applications may wish to use the Database as a cache of the most recent value. Each type has a getter method to retrieve the most recently assigned value.

note

Since the point may not be defined, the getters can fail. If you try to retrieve a point that doesn't exist using C++, Java, or .NET, an exception will be thrown.

Removing points​

Most applications don't need to remove points, but the option is there in case you want to remove points from a running outstation. There is a type-specific function for removing every point type given its index.

note

Removing a point stops the outstation from reporting static data for it. However, it does NOT remove any queued events for that point from the event buffer. Those events will remain in the event buffer until they are reported and cleared by confirmation.

Defining device attributes​

Specific attributes from Group 0 may be defined using the database. As you can see in the examples above, this should typically be done during database initialization. Rust uses a single method for defining attributes whereas the bindings have different methods for each type of attribute. Only attributes that are defined as writable are eligible to receive WRITE callbacks in the OutstationApplication interface.

The default set (set 0) holds attributes the standard defines. The database checks their types and lets only the user-assigned ones, such as location, owner and device name, be writable. Any other set is private, and you choose its variations.