写入与调用方法
能读取什么
写权限默认关闭。 库的标准目录只声明了有限的写属性,实际设备能写什么由设备点表决定。
两种"能不能写"
这是最容易混淆的地方 —— "能写"有两层完全不同的含义:
AttributeSchema::writable 默认是 false:
struct AttributeSchema {
std::uint8_t number = 2;
model::DataType type = model::DataType::null;
bool readable = true;
bool writable = false; ///< 写入须显式授权,已有读取 schema 不会自动变成可写
bool record = false;
};
一个属性"能读"不代表"能写"。 库不会因为某属性可读就默认它可写。
数据地址表
常见的可写属性:
这些属性的可写性必须逐个设备确认。库的标准目录能告诉你属性号和类型,但不代表你的设备开放了写权限。
最小完整示例
写校时模式:
#include <dlt698/app.hpp>
#include <iostream>
int main() {
using namespace dlt698;
namespace oi = standard::oi;
app::ClientOptions options;
options.protocol.server.bytes = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
app::Client client(options);
auto connected = client.connect_tcp("127.0.0.1", 6980);
if (!connected) {
std::cerr << connected.error().context << '\n';
return 1;
}
// 校时模式 = 1(自动校时)
auto dar = client.set(model::Oad{oi::date_time, 3, 0}, model::Data{model::Enum{1}});
if (!dar) {
std::cerr << "set failed: " << dar.error().context << '\n'; // 本地错误
client.disconnect();
return 1;
}
// 外层成功不等于写入成功:dar 可能是非零的协议原码
if (dar.value() != 0) {
std::cerr << "device rejected, DAR=" << unsigned(dar.value()) << '\n';
client.disconnect();
return 1;
}
std::cout << "time sync mode set\n";
client.disconnect();
return 0;
}
常用变体
写多个属性
auto response = client.set_list({
{model::Oad{oi::maximum_demand_period, 2, 0}, model::Data{model::UInt16{15}}},
{model::Oad{oi::sliding_interval, 2, 0}, model::Data{model::UInt16{60}}},
});
if (!response) {
std::cerr << response.error().context << '\n';
} else {
for (const auto& item : response.value().attributes) {
std::cout << "oi=" << std::hex << item.attribute.oi << std::dec
<< " attr=" << unsigned(item.attribute.attribute)
<< " DAR=" << unsigned(item.dar) << '\n';
}
}
批量写入不回滚
set_list 是逐项独立执行的。第一项成功、第二项失败时,第一项的写入不会被撤销。
需要"全部成功才提交"的语义,必须自己实现:先全部读一遍确认现值 → 逐项写 → 失败时手动写回原值。这个补偿逻辑得你自己写,库不提供事务。
调用方法
auto result = client.action(model::Omd{0x2000, 1, 0}, model::Data{model::UInt16{42}});
if (!result) {
std::cerr << "action failed: " << result.error().context << '\n';
} else if (result.value().dar != 0) {
std::cerr << "device rejected, DAR=" << unsigned(result.value().dar) << '\n';
} else if (result.value().data) {
// 方法有返回值
const auto& data = *result.value().data;
std::cout << "returned type=" << unsigned(data.type()) << '\n';
} else {
std::cout << "method executed, no return value\n";
}
库没有内置方法目录
DL/T 698.45 里的方法(OMD)由设备点表定义,库不预置方法清单。你需要从设备文档拿到"方法号 + 参数类型 + 返回类型",库只负责按你给的 OMD 编解码。
这也是本节示例用 0x2000/1/0 的原因 —— 那是为了演示编解码而构造的报文,不代表任何真实设备的方法。
多方法批量调用
auto response = client.action_list({
{model::Omd{0x2000, 1, 0}, model::Data{model::UInt16{42}}},
{model::Omd{0x2000, 2, 0}, model::Data{model::UInt8{1}}},
});
多参数时 APDU 的"多参数"标志位必须置 1。库的 set_list / action_list 会自动处理,手工构造 APDU 时需要注意。
服务端开放写权限
如果你是被读的一方(用 app::Server 模拟设备),必须在 schema 里显式声明可写:
#include <dlt698/service.hpp>
// 在你的 provider 里
service::ObjectSchema schema;
schema.oi = 0x4000;
schema.name = "date_time";
schema.attributes = {
{2, model::DataType::date_time_s, true, true, false}, // number, type, readable, writable, record
{3, model::DataType::enumeration, true, true, false},
};
writable 保持默认 false 时,即使客户端发了 SET,服务端也会拒绝。读取 schema 不会自动变成可写 —— 这是有意的设计,避免"能读就能改"的意外。
方法回调不要捕获 shared_ptr
// 正确:用弱引用打破引用环
std::weak_ptr<MyDevice> weak = device;
schema.methods_handler = [weak](const model::Omd& method, const model::Data& param) {
service::ActionValue out;
if (auto locked = weak.lock()) {
out = locked->handle(method, param);
} else {
out.dar = 5; // 设备已销毁
}
return out;
};
目录持有 provider,provider 捕获 shared_ptr 会形成引用环,两者都析构不掉。用 weak_ptr 并在 lock() 失败时返回错误 DAR。
请求与响应报文
以下示例固定:SA 六个 00 字节、逻辑地址 0、CA=0、PIID=0、控制域请求 43/响应 C3。
SET 写校时模式
请求:
68 19 00 43 05 00 00 00 00 00 00 00 AF 30 06 01 00 40 00 03 00 16 01 00 AF 55 16
响应(成功,DAR=0):
68 19 00 C3 05 00 00 00 00 00 00 00 78 F4 86 01 00 40 00 03 00 00 00 00 CD BD 16
响应(DAR=3,设备拒绝):
68 19 00 C3 05 00 00 00 00 00 00 00 78 F4 86 01 00 40 00 03 00 03 00 00 A9 52 16
只有 DAR 字节从 00 变成 03。外层解码依然成功 —— 这就是"外层 Result 成功不等于业务成功"的典型例子。
响应(DAR=7):
68 19 00 C3 05 00 00 00 00 00 00 00 78 F4 86 01 00 40 00 03 00 07 00 00 C8 31 16
SET 一次写两个属性
请求:
68 20 00 43 05 00 00 00 00 00 00 00 5B 4A 06 02 00 02 41 00 02 00 11 0F 41 01 02 00 11 01 00 11 B2 16
响应(部分失败:第一项成功,第二项 DAR=3):
68 1F 00 C3 05 00 00 00 00 00 00 00 0F 01 86 02 00 02 41 00 02 00 00 41 01 02 00 03 00 00 7A 86 16
批量写入的响应逐项独立,一项失败不影响其他项,也不会回滚已成功的写入。
ACTION 调用方法
请求(方法 2000/1/0,参数 UInt16 42):
68 1A 00 43 05 00 00 00 00 00 00 00 1C CE 07 01 00 20 00 01 00 12 00 2A 00 65 D5 16
响应(带返回数据):
68 1D 00 C3 05 00 00 00 00 00 00 00 2D AA 87 01 00 20 00 01 00 00 01 12 00 2A 00 00 88 25 16
这里 01 是"有 Data"的标志。它不存在表示方法无返回值 —— 这与"返回了一个 Null Data"是不同的结果,库用 std::optional<Data> 区分:nullopt 是没有返回,Data{Null{}} 是返回了空值。
响应(DAR=3):
68 1A 00 C3 05 00 00 00 00 00 00 00 CB 0A 87 01 00 20 00 01 00 03 00 00 00 27 46 16
DAR=3 时没有"有返回数据"标志,响应更短。
相关配置
取消写入不保证远端未执行
disconnect() 或超时会关闭物理通道,但 SET/ACTION 可能已经在设备侧执行了。协议层没有事务回滚机制,需要"要么全做要么全不做"的场景必须自己在应用层设计幂等或补偿。
常见失败