Executing controls
Construct sequences of binary and analog output headers using a builder class, then send them to a particular outstation using the association id.
Choosing a mode
operate sends the commands in one of two modes, and operate_no_ack sends them without asking for a response:
| Mode | Exchanges | Why |
|---|---|---|
| Select before operate | 2 | The outstation confirms it will accept exactly this command before it acts. Use it for most breaker and switch controls. |
| Direct operate | 1 | Speed matters and a mis-operation is tolerable. |
| Direct operate without acknowledgement | No response | Broadcast-style setpoints, where no response is acceptable. |
Operate
- Rust
- C
- C++
- Java
- C#
self.association
.operate(
CommandMode::SelectBeforeOperate,
CommandBuilder::single_header_u16(
Group12Var1::from_op_type(OpType::LatchOn),
3u16,
),
)
.await?;
void on_command_complete(dnp3_nothing_t nothing, void *context) { printf("%s: success\n", (const char *)context); }
void on_command_failure(dnp3_command_error_t error, void *context)
{
printf("%s: %s\n", (const char *)context, dnp3_command_error_to_string(error));
}
dnp3_command_set_t *commands = dnp3_command_set_create();
dnp3_group12_var1_t g12v1 = dnp3_group12_var1_init(dnp3_control_code_init(DNP3_TRIP_CLOSE_CODE_NUL, false, DNP3_OP_TYPE_LATCH_ON), 1, 1000, 1000);
dnp3_command_set_add_g12_v1_u16(commands, 3, g12v1);
dnp3_command_task_callback_t cb = {
.on_complete = &on_command_complete,
.on_failure = &on_command_failure,
.on_destroy = NULL,
.ctx = "cmd",
};
dnp3_param_error_t err = dnp3_master_channel_operate(channel, association_id, DNP3_COMMAND_MODE_SELECT_BEFORE_OPERATE, commands, cb);
dnp3_command_set_destroy(commands);
class CommandTaskCallback : public dnp3::CommandTaskCallback {
public:
explicit CommandTaskCallback(const char *cmd) : cmd(cmd) {}
void on_complete(dnp3::Nothing result) override { std::cout << cmd << ": success" << std::endl; }
void on_failure(dnp3::CommandError error) override { std::cout << cmd << ": " << dnp3::to_string(error) << std::endl; }
private:
const char *cmd;
};
std::unique_ptr<dnp3::CommandTaskCallback> create_command_callback(const char *cmd)
{
return std::make_unique<CommandTaskCallback>(cmd);
}
dnp3::CommandSet commands;
commands.add_g12_v1_u16(3, dnp3::Group12Var1(dnp3::ControlCode(dnp3::TripCloseCode::nul, false, dnp3::OpType::latch_on), 1, 1000, 1000));
channel.operate(assoc, dnp3::CommandMode::select_before_operate, commands, create_command_callback("cmd"));
CommandSet commands = new CommandSet();
Group12Var1 control = Group12Var1.fromCode(ControlCode.fromOpType(OpType.LATCH_ON));
commands.addG12V1U16(ushort(3), control);
channel
.operate(association, CommandMode.SELECT_BEFORE_OPERATE, commands)
.toCompletableFuture()
.get();
var commands = new CommandSet();
commands.AddG12V1U16(3, Group12Var1.FromCode(ControlCode.FromOpType(OpType.LatchOn)));
await channel.Operate(association, CommandMode.SelectBeforeOperate, commands);
Calling operate begins an asynchronous operation that will complete successfully or with an error code.
The master checks that the outstation echoed each command. Success means the outstation accepted it, not that the output changed; watch the output status points for that.
The operation fails if the outstation returns a status other than CommandStatus::Success for any of the commands. In Rust
the error is CommandError::Response(CommandResponseError::BadStatus(status)), carrying the status of the first command
that failed, and the other CommandResponseError values report a response that doesn't echo the request. The bindings
report BadStatus without the status, and HeaderMismatch for a response that doesn't echo the request. Consult the
language-specific documentation for the meaning of each error code.
Direct operate without acknowledgement
operate_no_ack sends the commands using DIRECT_OPERATE_NO_ACK. The outstation never responds to this function code, not even to report an error.
- Rust
- C
- C++
- Java
- C#
self.association
.operate_no_ack(CommandBuilder::single_header_u16(
Group12Var1::from_op_type(OpType::LatchOn),
3u16,
))
.await?;
void on_command_sent(dnp3_nothing_t nothing, void *context)
{
// the request was sent: the outstation doesn't answer
printf("%s: sent\n", (const char *)context);
}
void on_no_ack_command_failure(dnp3_command_error_t error, void *context)
{
printf("%s: %s\n", (const char *)context, dnp3_command_error_to_string(error));
}
dnp3_command_set_t *commands = dnp3_command_set_create();
dnp3_group12_var1_t g12v1 = dnp3_group12_var1_init(dnp3_control_code_init(DNP3_TRIP_CLOSE_CODE_NUL, false, DNP3_OP_TYPE_LATCH_ON), 1, 1000, 1000);
dnp3_command_set_add_g12_v1_u16(commands, 3, g12v1);
dnp3_command_task_callback_t cb = {
.on_complete = &on_command_sent,
.on_failure = &on_no_ack_command_failure,
.on_destroy = NULL,
.ctx = "cna",
};
dnp3_param_error_t err = dnp3_master_channel_operate_no_ack(channel, association_id, commands, cb);
dnp3_command_set_destroy(commands);
class NoAckCommandTaskCallback : public dnp3::CommandTaskCallback {
public:
explicit NoAckCommandTaskCallback(const char *cmd) : cmd(cmd) {}
// the request was sent: the outstation doesn't answer
void on_complete(dnp3::Nothing result) override { std::cout << cmd << ": sent" << std::endl; }
void on_failure(dnp3::CommandError error) override { std::cout << cmd << ": " << dnp3::to_string(error) << std::endl; }
private:
const char *cmd;
};
std::unique_ptr<dnp3::CommandTaskCallback> create_no_ack_command_callback(const char *cmd)
{
return std::make_unique<NoAckCommandTaskCallback>(cmd);
}
dnp3::CommandSet commands;
commands.add_g12_v1_u16(3, dnp3::Group12Var1(dnp3::ControlCode(dnp3::TripCloseCode::nul, false, dnp3::OpType::latch_on), 1, 1000, 1000));
channel.operate_no_ack(assoc, commands, create_no_ack_command_callback("cna"));
CommandSet commands = new CommandSet();
Group12Var1 control = Group12Var1.fromCode(ControlCode.fromOpType(OpType.LATCH_ON));
commands.addG12V1U16(ushort(3), control);
channel.operateNoAck(association, commands).toCompletableFuture().get();
var commands = new CommandSet();
commands.AddG12V1U16(3, Group12Var1.FromCode(ControlCode.FromOpType(OpType.LatchOn)));
await channel.OperateNoAck(association, commands);
The operation completes successfully once the request has been written to the transport. Success does not mean that the outstation received, accepted, or executed the commands. Use operate when you need the command status reported by the outstation.