15 KiB
Tibber API + 荷兰电价 + DSMR Reader 参考
本文件汇总 M6(动态/固定电价 → 实时买卖电费计算)所需的全部外部事实,供
docs/design/m6-tibber-dynamic-energy.md与实现者直接 refer。 来源:Tibber GraphQL introspection(实证)、Tibber Explorer demo 数据、Tibber NL support 文档、用户实际合同与 DSMR Reader 配置(2026-06 整理)。 凡标「✅ 实证」的为代码/数据验证过的事实;金额类以用户真实账单 / 合同生效后真实 token 为最终准(见末尾「待真实数据核对」)。
1. Tibber GraphQL API
- Endpoint:
POST https://api.tibber.com/v1-beta/gql - 认证:HTTP header
Authorization: Bearer <token>;token 在 developer.tibber.com 登录后生成(每个 Tibber 账户一个)。 - 在线 Explorer:developer.tibber.com/explorer,自带一个公共 demo token。
- ⚠️ demo token 现在只能做 schema introspection 与历史 demo 数据查询;对
viewer的真实数据查询返回UNAUTHENTICATED。要拿自己家的实时价,必须用自己账户的 token。
- ⚠️ demo token 现在只能做 schema introspection 与历史 demo 数据查询;对
1.1 Price 类型(✅ introspection 实证)
Price 对象恰好 6 个字段:
| 字段 | 类型 | 含义 |
|---|---|---|
total |
Float | 全包价 = energy + tax(即 App 里的 All-in Price,含 spot+能源税+VAT 等) |
energy |
Float | 纯能源/现货分量(ex-VAT 市场价) |
tax |
Float | 税费分量(能源税 + VAT 合并) |
startsAt |
String | 该价格段起点(ISO8601,含时区偏移,如 +02:00) |
currency |
String! | 币种(唯一 non-null 字段;NL 为 EUR,demo 为 SEK) |
level |
PriceLevel | 价格档枚举(VERY_CHEAP/CHEAP/NORMAL/EXPENSIVE/VERY_EXPENSIVE) |
✅ 实证:total == energy + tax 精确成立(见 §1.4 样本)。
1.2 15 分钟价格:Subscription.priceInfoRange(✅ introspection 实证)
- 字段:
currentSubscription.priceInfoRange - 参数:
resolution: PriceInfoRangeResolution!(必填)、first: Int、last: Int、before: String、after: String(游标分页) - resolution 枚举
PriceInfoRangeResolution:值DAILY/HOURLY/QUARTER_HOURLY- ⚠️ 注意别用错枚举:另有
PriceResolution(只有 HOURLY/DAILY,无 QUARTER_HOURLY)和PriceInfoResolution——priceInfoRange用的是PriceInfoRangeResolution。
- ⚠️ 注意别用错枚举:另有
- 返回:
SubscriptionPriceConnection { nodes: [Price]!, edges: [...], pageInfo } - 废弃说明:老的
priceInfo.range(deprecationReason: "use Subscription.priceInfoRange instead")不支持 QUARTER_HOURLY,必须改用Subscription.priceInfoRange。 - ✅ 实证(demo key,2018 历史日):
QUARTER_HOURLY返回 96 个节点、间隔精确 15 分钟;无真 15 分钟价的历史日,API 把小时价重复成 4 个相同刻钟(00/15/30/45 同值,到下一整点才变);NL 当前数据则是真 15 分钟。 - 解析注意:不要假设固定 96 个节点 / 固定 15 分钟间隔,按
startsAt逐点存最稳。
15 分钟查询(拉今天 96 个刻钟):
{ viewer { homes { id currentSubscription {
priceInfoRange(resolution: QUARTER_HOURLY, first: 96) {
nodes { startsAt total energy tax currency level }
} } } } }
1.3 小时价 / 当前价 / 探测:priceInfo
currentSubscription.priceInfo 含 current(当前价)、today[]、tomorrow[](次日价约 13:00 CET 发布)。探测账户能力 + 当前价:
{ viewer { homes {
id appNickname
features { realTimeConsumptionEnabled }
currentSubscription { priceInfo {
current { total energy tax startsAt level currency }
today { total energy tax startsAt level }
tomorrow { total energy tax startsAt level }
} } } } }
1.4 现成 curl + 样本响应
# 15 分钟价(换成自己账户 token;demo token 拿不到 viewer 真实数据)
curl -s -X POST https://api.tibber.com/v1-beta/gql \
-H "Authorization: Bearer <YOUR_TOKEN>" -H "Content-Type: application/json" \
-d '{"query":"{ viewer { homes { id currentSubscription { priceInfoRange(resolution: QUARTER_HOURLY, first: 96) { nodes { startsAt total energy tax currency } } } } } }"}'
样本:priceInfo(demo,瑞典家庭,SEK)
{"current": {"total": 0.8239, "energy": 0.5663, "tax": 0.2576,
"startsAt": "2026-06-23T14:00:00.000+02:00", "level": "NORMAL", "currency": "SEK"},
"today": [{"total": 1.276, "energy": 0.928, "tax": 0.348,
"startsAt": "2026-06-23T00:00:00.000+02:00", "level": "VERY_EXPENSIVE"}, … 24 项],
"tomorrow":[… 24 项]}
样本:priceInfoRange QUARTER_HOURLY(demo,2018-11-02,96 节点)
{"nodes": [
{"startsAt":"2018-11-02T00:00:00.000+01:00","total":0.63, "energy":0.435,"tax":0.195,"currency":"SEK"},
{"startsAt":"2018-11-02T00:15:00.000+01:00","total":0.63, "energy":0.435,"tax":0.195,"currency":"SEK"},
{"startsAt":"2018-11-02T00:30:00.000+01:00","total":0.63, "energy":0.435,"tax":0.195,"currency":"SEK"},
{"startsAt":"2018-11-02T00:45:00.000+01:00","total":0.63, "energy":0.435,"tax":0.195,"currency":"SEK"},
{"startsAt":"2018-11-02T01:00:00.000+01:00","total":0.6141,"energy":0.4223,"tax":0.1918,"currency":"SEK"},
… 共 96 个,末节点 23:45 total 0.6342]}
(注意 00:00–00:45 四个刻钟同值 = 小时价被重复;total==energy+tax。)
2. 荷兰电价构成(Tibber NL + 用户合同)
所有金额含 VAT(incl. btw),除非另注。VAT/BTW 标准税率 21%。
| 分量 | 荷兰语 | 单位 | 值 / 说明 |
|---|---|---|---|
| 现货/市场价 | marktprijs / dynamische kwartierprijs | €/kWh | 每 15 分钟跟随交易所;= API energy |
| 买侧采购费 | inkoopvergoeding | €/kWh | €0.0248(覆盖 onbalans + 绿证;见下「相等」事实) |
| 卖侧上网费 | verkoopvergoeding | €/kWh | €0.0248(2026-01-01 起) |
| 能源税 | energiebelasting | €/kWh(含 VAT) | 2026 第一档(0–10000 kWh)≈ €0.1108;2025 ≈ €0.1228;高档更低 |
| 增值税 | BTW | % | 21% |
| 固定月费 | vast bedrag / leveringskosten | €/月/合同 | Tibber €5.99/月(电、气各一次) |
| 电网维护费 | netbeheerkosten / systeembeheerkosten | €/天或/月 | 电网公司定、供应商代收;按地区,2026 约 +3.38% |
| 能源税抵扣 | heffingskorting / vermindering energiebelasting | €/年/连接 | 政府年度减免,从总费用扣 |
| ODE | Opslag Duurzame Energie | €/kWh | 现为 0(已并入 energiebelasting) |
✅ 关键事实(Tibber NL 文档原文):
"De verkoopvergoeding van 2,48 cent is gelijk aan de inkoopvergoeding die je bij je afgenomen stroom betaalt." (卖侧 verkoopvergoeding 2.48 分 = 买侧 inkoopvergoeding。)
→ 买卖服务费相等(均 €0.0248/kWh),在买卖里一进一出相互抵消。
3. 净计量(saldering)、回送(teruglevering)、负电价、2027
3.1 回送价(净计量期内,文档原文)
"Op het moment dat je teruglevert geven we je per kWh de beursprijs die op dat moment geldt …, inclusief energiebelasting en inkoopvergoeding plus de btw minus de verkoopvergoeding."
即净计量期内回送价 = beursprijs + energiebelasting + inkoopvergoeding + btw − verkoopvergoeding。因 inkoopvergoeding = verkoopvergoeding 抵消 → = 全额零售价(spot+能源税+VAT),正是 saldering "回送 1 度 = 用 1 度"的本质。
3.2 年末盈余 / 取消净计量后(文档原文,Scenario 2)
"Voor de overproductie van 500 kWh heb je recht op de beursprijs en de inkoopvergoeding, maar heb je geen recht op de energiebelasting. … ontvang je nog een factuur van ons voor de te veel uitgekeerde belastingen …"
即超额回送(或 2027 取消净计量后):盈余按 beursprijs + inkoopvergoeding − verkoopvergoeding(无能源税)计价 → 因两费抵消 → = 纯 spot。
3.3 净计量机制
- 法定按自然年结算;可抵扣到「用电量」为止(回送抵到用电为止)。
- Tibber 不用预付,净计量周期在第 12 张账单后结束。
- 2027 起荷兰取消净计量(saldering stopt in 2027)——用户据此设计:不再考虑净计量,直接按实时买卖算。
3.4 负电价
- 负价时用电:理论上你拿钱(但仍计能源税,净计量期内税会退回)。
- 负价时回送:你要为回送的电付那个负价(= 倒贴)。
- 因 Tibber 给的是 all-in
total(已含能源税),只有 spot 负到比能源税还多,total 才转负;中等负价时 total 仍正(照付)。 - 结论:负电价不需要特殊处理——
buy=total与sell=total−能源税符号自洽。
4. 本项目采用的买/卖价公式(M6)
spot 取 API
energy;total = energy + tax(全包)。买价直接用total,卖价从total扣掉卖电不交的能源税。
- Tibber 动态合同(post-2027 口径):
- 买价
buy = price.total - 卖价
sell = price.total − energy_tax_per_kwh − sell_adjust(sell_adjust默认 0;含 VAT 归己;买卖费抵消已隐含在 total 里)
- 买价
- 固定合同(manual,双费率):
- 买价
buy_档 = energy_buy_档 + energy_tax(档 ∈ {normal, dal}) - 卖价
sell_档 = sell_档(回送价,无能源税)
- 买价
- 固定费/抵扣不进每度:
network_fee、management_fee(月→天)、heffingskorting(年→天)在日/月/年汇总层加减。 - 详见
docs/design/m6-tibber-dynamic-energy.md§3.4。
5. 用户当前固定合同(manual 实例,2026-06)
双费率(NL:
_1=dal/低谷、_2=normal/高峰)。以下为当前值,会随合同阶段/年度变(系统按"加新版本+生效日期"留底)。
| 项 | 当前值 | 备注 |
|---|---|---|
| 能源价 normal(高) | €0.133/kWh | 买侧,含 VAT,不含能源税 |
| 能源价 dal(低) | €0.127/kWh | 买侧 |
| 回送价 normal | (现与 dal 同) | 卖侧,分档但现同价;系统仍分开填 |
| 回送价 dal | — | 卖侧 |
| 能源税 energiebelasting | 待填(≈€0.1108) | 含 VAT,加到买价 |
| ODE | 0 | 已并入能源税 |
| 电网维护费 | 待填 | 按天(合同按天收) |
| 供应商管理费 | ≈€0.329/天(≈€9.87/月) | 对应 Tibber 的 €5.99/月 |
| heffingskorting | 待填 | 年度减免,汇总时扣 |
| 回送阶梯罚金 terugleverkosten | 不做 | 按自然年累计、阶梯式;用户住不到年底算不准,M6 不实现 |
6. DSMR Reader → MQTT
6.1 取数方式
- 用 Telegram JSON(单 topic
dsmr/json,一条 = 一帧完整 telegram 的 JSON),不用 split-topic(每字段一 topic、要按 id 拼)、不用 raw(未解析 OBIS)。 - DSMR Reader 每秒一条。本项目按 10 秒降采样(仅秒数整 10 落盘)、整帧存 JSON blob(不做字段 allowlist)。
6.2 telegram JSON 字段(DSMR Reader 配置的 JSON mapping)
id, timestamp,
electricity_delivered_1, electricity_delivered_2, # 进口累计 kWh(_1=dal 低谷, _2=normal 高峰)
electricity_returned_1, electricity_returned_2, # 出口/回送累计 kWh
electricity_currently_delivered, electricity_currently_returned, # 瞬时进/出口功率 kW
phase_currently_delivered_l1/l2/l3, phase_currently_returned_l1/l2/l3, # 各相瞬时功率 kW
phase_voltage_l1/l2/l3, # 各相电压 V
phase_power_current_l1/l2/l3, # 各相电流 A
extra_device_timestamp, extra_device_delivered # 燃气表(m³,每 5 分钟更新)
6.3 实测样本(单相,dsmr/json)
{
"id": 200086230,
"timestamp": "2026-06-23T12:16:48Z",
"electricity_delivered_1": "20915.154",
"electricity_returned_1": "2979.905",
"electricity_delivered_2": "15212.090",
"electricity_returned_2": "6786.406",
"electricity_currently_delivered": "0.000",
"electricity_currently_returned": "2.704",
"phase_currently_delivered_l1": "0.000",
"phase_currently_delivered_l2": null,
"phase_currently_delivered_l3": null,
"extra_device_timestamp": "2026-06-23T12:15:00Z",
"extra_device_delivered": "6208.234",
"phase_currently_returned_l1": "2.704",
"phase_currently_returned_l2": null,
"phase_currently_returned_l3": null,
"phase_voltage_l1": "237.0",
"phase_voltage_l2": null,
"phase_voltage_l3": null
}
6.4 解析要点(实测确认)
- 数值都是 JSON 字符串(
"20915.154"、"0.000")→ 计费读取转 Decimal(累计寄存器算钱要精度)。 - 缺测相位 =
null(不是缺 key)→ 视为缺测;单相只有*_l1,三相后_l2/_l3由 null 变数字。 - 进口总量 =
electricity_delivered_1 + _2;出口总量 =electricity_returned_1 + _2(双费率档对动态合同无意义,求和;对固定合同分档计价)。 timestamp为 UTC(Z);id自增,做幂等去重。- 历史样本里电压 key 曾被错配成带前缀
dsmr/reading/phase_voltage_l1(DSMR Reader 的 JSON mapping 配置笔误,用户已改对)——解析仍按"键名容错"。 - 整表寄存器与相数无关:
delivered/returned_1/2是整表总量,三相只多了各相瞬时通道 → 计费逻辑相数无关。 - 燃气
extra_device_delivered(m³)一并存入 blob;燃气计费 M6 不做(数据先留,未来按 commodity 扩展)。
7. 来源 URL
- Tibber GraphQL reference / explorer:
https://developer.tibber.com/docs/reference#rootsubscription、https://developer.tibber.com/explorer - Tibber NL 费用构成:
https://support.tibber.com/nl/articles/5605892-de-kosten-bij-tibber - Tibber NL 净计量与回送:
https://support.tibber.com/nl/articles/4669873-salderen-en-terugleveren-bij-tibber - DSMR Reader(HA 集成):
https://www.home-assistant.io/integrations/dsmr_reader/
8. 待真实数据核对(合同生效后用真实 token / 账单)
- 真实 token 复核:跑 §1.4 的 15 分钟 curl,确认 NL 返回真 15 分钟价(非重复小时价)+ 币种 EUR。
- 卖价残差:确认
total里 purchase fee 是否被卖侧 sales fee 完全抵掉、回送 VAT 口径 → 调sell_adjust(默认 0)。 - 双费率寄存器映射:确认
_1=dal/_2=normal 没接反(差价小但要对)。 - 能源税年值:按当年实际值与年用电档位核
energy_tax。 - 固定合同数值:回送两档价、电网费、heffingskorting 待用户从账单填。