Návod

Spotové ceny v Node-RED

Flow k importu, který odebírá aktuální cenu z MQTT brokeru spotovky.cz a umí ji načíst i z REST API.

Import flow

Flow níže vytvoří novou záložku spotovky.cz se třemi větvemi:

  1. MQTT JSON — uzel mqtt in odebírá v2/current/json a předává objekt s cenou, začátkem a koncem intervalu a pořadím.
  2. MQTT binárně — odebírá v2/current/bin (16 bytů) a funkce ho dekóduje na stejný objekt. Ukazuje práci s binárním formátem v2.
  3. REST — každých 15 minut stáhne /v2/prices/today a funkce vybere aktuální interval.

V Node-RED otevřete menu vpravo nahoře → Import, vložte JSON a potvrďte. Pak klikněte na Deploy. Výsledky se ukážou v postranním panelu Debug a cena ve stavu uzlů debug.

Node-RED flow (JSON)
[
  {
    "id": "spotovky_tab",
    "type": "tab",
    "label": "spotovky.cz",
    "disabled": false,
    "info": "Spotové ceny elektřiny z API spotovky.cz"
  },
  {
    "id": "spotovky_broker",
    "type": "mqtt-broker",
    "name": "spotovky.cz",
    "broker": "spotovky.cz",
    "port": "1883",
    "clientid": "",
    "autoConnect": true,
    "usetls": false,
    "protocolVersion": "4",
    "keepalive": "60",
    "cleansession": true,
    "autoUnsubscribe": true,
    "birthTopic": "",
    "birthQos": "0",
    "birthPayload": "",
    "birthMsg": {},
    "closeTopic": "",
    "closeQos": "0",
    "closePayload": "",
    "closeMsg": {},
    "willTopic": "",
    "willQos": "0",
    "willPayload": "",
    "willMsg": {},
    "userProps": "",
    "sessionExpiry": ""
  },
  {
    "id": "spotovky_mqtt_json",
    "type": "mqtt in",
    "z": "spotovky_tab",
    "name": "v2/current/json",
    "topic": "v2/current/json",
    "qos": "1",
    "datatype": "json",
    "broker": "spotovky_broker",
    "nl": false,
    "rap": true,
    "rh": 0,
    "inputs": 0,
    "x": 140,
    "y": 80,
    "wires": [
      [
        "spotovky_dbg_json"
      ]
    ]
  },
  {
    "id": "spotovky_dbg_json",
    "type": "debug",
    "z": "spotovky_tab",
    "name": "Aktuální cena (JSON)",
    "active": true,
    "tosidebar": true,
    "console": false,
    "tostatus": true,
    "complete": "payload",
    "targetType": "msg",
    "statusVal": "payload.czk",
    "statusType": "msg",
    "x": 420,
    "y": 80,
    "wires": []
  },
  {
    "id": "spotovky_mqtt_bin",
    "type": "mqtt in",
    "z": "spotovky_tab",
    "name": "v2/current/bin",
    "topic": "v2/current/bin",
    "qos": "1",
    "datatype": "buffer",
    "broker": "spotovky_broker",
    "nl": false,
    "rap": true,
    "rh": 0,
    "inputs": 0,
    "x": 140,
    "y": 160,
    "wires": [
      [
        "spotovky_fn_bin"
      ]
    ]
  },
  {
    "id": "spotovky_fn_bin",
    "type": "function",
    "z": "spotovky_tab",
    "name": "Dekódovat binární v2",
    "func": "// v2/current/bin: 16 bytů, little-endian\nconst b = msg.payload;\nif (!Buffer.isBuffer(b) || b.length < 16 || b[0] !== 2) return null;\nmsg.payload = {\n    start: new Date(Number(b.readBigUInt64LE(2)) * 1000).toISOString(),\n    czk: b.readInt16LE(10) / 100,      // Kč/kWh, může být záporná\n    rank: b.readUInt8(12),             // 1 = nejlevnější\n    rate: b.readUInt16LE(13) / 1000,   // kurz EUR/CZK\n    periods: b.readUInt8(15),          // 92, 96 nebo 100\n    complete: (b[1] & 0x01) !== 0,\n    negative_day: (b[1] & 0x02) !== 0\n};\nreturn msg;",
    "outputs": 1,
    "timeout": 0,
    "noerr": 0,
    "initialize": "",
    "finalize": "",
    "libs": [],
    "x": 380,
    "y": 160,
    "wires": [
      [
        "spotovky_dbg_bin"
      ]
    ]
  },
  {
    "id": "spotovky_dbg_bin",
    "type": "debug",
    "z": "spotovky_tab",
    "name": "Aktuální cena (binárně)",
    "active": true,
    "tosidebar": true,
    "console": false,
    "tostatus": true,
    "complete": "payload",
    "targetType": "msg",
    "statusVal": "payload.czk",
    "statusType": "msg",
    "x": 640,
    "y": 160,
    "wires": []
  },
  {
    "id": "spotovky_inject",
    "type": "inject",
    "z": "spotovky_tab",
    "name": "Každých 15 min",
    "props": [
      {
        "p": "payload"
      }
    ],
    "repeat": "900",
    "crontab": "",
    "once": true,
    "onceDelay": "2",
    "topic": "",
    "payload": "",
    "payloadType": "date",
    "x": 150,
    "y": 240,
    "wires": [
      [
        "spotovky_http"
      ]
    ]
  },
  {
    "id": "spotovky_http",
    "type": "http request",
    "z": "spotovky_tab",
    "name": "GET /v2/prices/today",
    "method": "GET",
    "ret": "obj",
    "paytoqs": "ignore",
    "url": "https://api.spotovky.cz/v2/prices/today",
    "tls": "",
    "persist": false,
    "proxy": "",
    "insecureHTTPParser": false,
    "authType": "",
    "senderr": false,
    "headers": [],
    "x": 380,
    "y": 240,
    "wires": [
      [
        "spotovky_fn_rest"
      ]
    ]
  },
  {
    "id": "spotovky_fn_rest",
    "type": "function",
    "z": "spotovky_tab",
    "name": "Aktuální interval",
    "func": "// Najde aktuální 15minutový interval v odpovědi /v2/prices/today\nif (msg.statusCode !== 200) {\n    node.warn(\"API vrátilo HTTP \" + msg.statusCode);\n    return null;\n}\nconst now = Date.now();\nconst slots = msg.payload.slots || [];\nconst cur = slots.find(s => {\n    const t = Date.parse(s.start);\n    return t <= now && now < t + 15 * 60 * 1000;\n});\nif (!cur) return null;\nmsg.payload = {\n    date: msg.payload.date,\n    start: cur.start,\n    czk: cur.czk,\n    eur: cur.eur,\n    rank: cur.rank,\n    periods: slots.length\n};\nreturn msg;",
    "outputs": 1,
    "timeout": 0,
    "noerr": 0,
    "initialize": "",
    "finalize": "",
    "libs": [],
    "x": 600,
    "y": 240,
    "wires": [
      [
        "spotovky_dbg_rest"
      ]
    ]
  },
  {
    "id": "spotovky_dbg_rest",
    "type": "debug",
    "z": "spotovky_tab",
    "name": "Aktuální cena (REST)",
    "active": true,
    "tosidebar": true,
    "console": false,
    "tostatus": true,
    "complete": "payload",
    "targetType": "msg",
    "statusVal": "payload.czk",
    "statusType": "msg",
    "x": 830,
    "y": 240,
    "wires": []
  }
]

Konfigurační uzel brokeru má prázdné zprávy birth, close i will: broker spotovky.cz přijímá jen subscribe a publish odmítá. Na tento broker nepřipojujte uzel mqtt out.

Co dál

  • Ceny celého dne odebírejte z v2/today/json a v2/tomorrow/json (stejný formát jako REST /v2/prices/…). Zpráva pro zítřek přichází obvykle po 13:05 a po půlnoci se maže prázdnou retain zprávou, kterou je dobré ve flow ignorovat.
  • Pole rank (1 = nejlevnější) se hodí pro uzel switch: např. „rank ≤ 16“ znamená 16 nejlevnějších čtvrthodin dne.
  • Počet intervalů dne není vždy 96: v den změny času je 92 nebo 100. Používejte periods nebo délku pole slots.

Další návody