M6: add Tibber API + NL energy pricing + DSMR reference doc
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
# 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](https://developer.tibber.com) 登录后生成(每个 Tibber 账户一个)。
|
||||
- **在线 Explorer**:[developer.tibber.com/explorer](https://developer.tibber.com/explorer),自带一个公共 **demo token**。
|
||||
- ⚠️ **demo token 现在只能做 schema introspection 与历史 demo 数据查询**;对 `viewer` 的真实数据查询返回 `UNAUTHENTICATED`。要拿自己家的实时价,必须用**自己账户的 token**。
|
||||
|
||||
### 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 个刻钟)**:
|
||||
```graphql
|
||||
{ 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 发布)。探测账户能力 + 当前价:
|
||||
```graphql
|
||||
{ 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 + 样本响应
|
||||
|
||||
```bash
|
||||
# 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)**
|
||||
```json
|
||||
{"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 节点)**
|
||||
```json
|
||||
{"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`)
|
||||
```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 / 账单)
|
||||
|
||||
1. **真实 token 复核**:跑 §1.4 的 15 分钟 curl,确认 NL 返回**真** 15 分钟价(非重复小时价)+ 币种 EUR。
|
||||
2. **卖价残差**:确认 `total` 里 purchase fee 是否被卖侧 sales fee 完全抵掉、回送 VAT 口径 → 调 `sell_adjust`(默认 0)。
|
||||
3. **双费率寄存器映射**:确认 `_1`=dal/`_2`=normal 没接反(差价小但要对)。
|
||||
4. **能源税年值**:按当年实际值与年用电档位核 `energy_tax`。
|
||||
5. **固定合同数值**:回送两档价、电网费、heffingskorting 待用户从账单填。
|
||||
</content>
|
||||
Reference in New Issue
Block a user