This is a development version of the documentation. Content may change without notice.
Voke Documentation
Partner API

Rychlý start — Voke ESM (CZ)

Kompletní průchod integrací proti živé Voke organizaci. Odhadovaný čas: 15 minut.

Tato příručka vás provede od nuly až po živou telemetrii proti Voke organizaci. Na konci budete mít potvrzené AMQP fronty, publikovaný příkaz a běžící konzumenta telemetrie — vše s použitím přihlašovacích údajů vygenerovaných průvodcem Connect partner na /orgs/<orgId>/settings/connections.

Chcete celý projekt ke klonování? lefteq/voke-partner-examples obsahuje ověřené, spustitelné Java (Spring Boot) a TypeScript klienty — snippety níže shrnuté do git clone-a-spusť repozitáře.

Předpoklady

  • Běžící instance Voke (lokálně: bun run docker:up && bun run dev:api, nebo vzdálené nasazení).
  • Org admin cílové Voke organizace, který vám může vygenerovat partnerské klíče.
  • Node 18 nebo novější pro spuštění TypeScript příkladů.

Krok 1: Získejte partnerské přihlašovací údaje od svého org admina

Produkční partneři si AMQP nepovolují sami. Požádejte svého org admina, aby pro vaši integraci vygeneroval klíč pomocí průvodce Connect partner:

  1. Přihlaste se na https://voke.turena.cz a vyberte správnou organizaci v přepínači organizací.
  2. Otevřete Organization settings → Connections (/orgs/<orgId>/settings/connections?tab=api-keys).
  3. Klikněte na Connect partner a projděte třístupňového průvodce. Každý krok má vlastní URL, takže se na něj můžete přímo odkázat nebo stránku obnovit, aniž byste ztratili postup; stav je uložen v sessionStorage a automaticky vymazán při úspěšném Generate nebo Cancel:
    • Use case/orgs/<orgId>/settings/connections/connect-partner — vyberte přednastavení, které odpovídá vaší integraci (Trading platform partner, Telemetry consumer (read-only), HTTP read-only, Internal tool nebo Custom).
    • Permissions.../connect-partner/permissions — zkontrolujte předvybranou sadu ApiKeyScope; v případě potřeby ji zužte.
    • Network.../connect-partner/network — volitelně omezte klíč na CIDR allowlist, vyplňte čitelný název + partner ID a klikněte na Generate. Voke spustí serverový broker self-test, než přesměruje na detail API klíče, kde se zobrazí banner s jednorázovým odhalením.
  4. Předejte partnerovi balíček (bundle) z odhalovacího banneru: AMQPS URI, raw API key a (pokud je přítomen scope vcp:write:*) HMAC signing key. Klikněte na I've saved these pro zavření; obnovení detailu po tomto kroku tajné hodnoty znovu nezobrazí. Ukázky kódu na záložce Quickstart téže stránky jsou předvyplněné org slugem — zkopírujte je jako výchozí bod.

Balíček se zobrazí přesně jednou. Uložte AMQPS URI, raw key a signing key do správce tajných hodnot (secret manager) předtím, než na odhalovací obrazovce kliknete na I've saved these. Obnova neexistuje — pokud se jakákoli hodnota ztratí, klíč rotujte (vygenerujte nový, starý zneplatněte). Vzor pro rotaci viz API keys & auth.

Záložka Quickstart na /orgs/<orgId>/settings/connections?tab=quickstart je zdrojem pravdy pro Node, Python a curl úryvky připravené k vložení — sestavuje je server se skutečným org slugem a placeholderem <your-key>. Níže uvedené ukázky zrcadlí tento výstup s dalším inline komentářem. Kompletní mapu admin tras viz Organization settings routes.

Krok 2: Nastavte si integrátorský projekt

Vytvořte nový Node projekt a nainstalujte klientskou knihovnu AMQP:

mkdir voke-esm-quickstart && cd voke-esm-quickstart
bun init
bun add amqplib
bun add -d @types/amqplib tsx typescript

Krok 3: Přidejte pomocník pro připojení

Vytvořte amqp-connect.ts. Nejjednodušší cesta je předat AMQPS URI přímo z odhalovací obrazovky — již obsahuje org slug jako AMQP uživatelské jméno, raw key jako heslo a vhost specifický pro klíč (partner-{keyId}) jako cestu:

// amqp-connect.ts
// Connect to Voke's RabbitMQ using the AMQPS URI from the Connect partner wizard.
// The URI shape is:
//   amqps://{org-slug}:{url-encoded-key}@amqp.voke.turena.cz:5671/partner-{keyId}
import * as amqp from 'amqplib';

export interface VokeAmqpCreds {
  /**
   * The full AMQPS URI from the Connections reveal screen.
   * Contains org slug (username), raw API key (password), host, port, and
   * per-key vhost (`partner-{keyId}`) — no manual assembly required.
   */
  amqpsUri: string;
  /** Your organization slug — used for queue/routing-key prefixes only. */
  orgSlug: string;
}

export async function connectVoke(creds: VokeAmqpCreds) {
  // The TLS-1.3-only broker requires SNI; amqplib will not infer the servername
  // from the URI, so pass it explicitly or the TLS handshake fails.
  const conn = await amqp.connect(creds.amqpsUri, {
    servername: new URL(creds.amqpsUri).hostname,
  });
  const ch = await conn.createChannel();
  const prefix = `vcp.${creds.orgSlug}`;

  // Queues are asserted (and bound to the `vcp` exchange) by Voke when
  // trading is enabled for your org. checkQueue verifies they exist before
  // any publish or consume.
  const inbound = {
    commands: `${prefix}.command`,
    config: `${prefix}.config`,
    schedule: `${prefix}.schedule`,
  };
  const outbound = {
    telemetry: `${prefix}.event.telemetry`,
    status: `${prefix}.event.status`,
    alarm: `${prefix}.event.alarm`,
    execution: `${prefix}.event.execution`,
  };

  for (const q of [...Object.values(inbound), ...Object.values(outbound)]) {
    await ch.checkQueue(q);
  }

  return { conn, ch, exchange: 'vcp', prefix, inbound, outbound };
}

Krok 4: Ověřte připojení

Vytvořte run-connect.ts a spuštěním ověřte, že AMQP autentizace funguje a všechny fronty existují:

// run-connect.ts
import { connectVoke } from './amqp-connect';

(async () => {
  const { conn, inbound, outbound } = await connectVoke({
    // Paste the AMQPS URI from the Connections reveal screen.
    amqpsUri: 'amqps://YOUR-ORG-SLUG:URL-ENCODED-KEY@amqp.voke.turena.cz:5671/partner-XXXX',
    orgSlug: 'YOUR-ORG-SLUG',
  });
  console.log('inbound queues OK:', inbound);
  console.log('outbound queues OK:', outbound);
  await conn.close();
})();
npx tsx run-connect.ts
# Expected:
# inbound queues OK: { commands: 'vcp.acme.command', config: 'vcp.acme.config', schedule: 'vcp.acme.schedule' }
# outbound queues OK: { telemetry: 'vcp.acme.event.telemetry', status: 'vcp.acme.event.status', ... }

Pokud checkQueue vyhodí chybu, fronty pro vaši org nejsou nasazené (asserted). Broker self-test v průvodci by to měl zachytit ještě před odhalením balíčku — pokud na to narazíte v praxi, požádejte svého org admina, aby pomocí tlačítka Test connection na detailu API klíče (/orgs/<orgId>/settings/connections/api-keys/<apiKeyId>) znovu spustil sondu a vypsal chybu brokeru.

Krok 5: Přidejte publisher příkazů

Vytvořte publish-command.ts. Příkazy se publikují do topic exchange vcp s routing key {slug}.command.{commandType} — konzument Voke je vyzvedne z vcp.{slug}.command. Site setpointy jsou autentizovány dvojicí AMQP přihlašovacích údajů a scopem vcp:write:setpoint. Rizikovější trasy (command.device, command.mode, schedule.*) vyžadují navíc HMAC podpis; viz VCP message integrity.

// publish-command.ts
// Publish a VCP command by routing it through the `vcp` exchange.
import { randomUUID } from 'node:crypto';
import { connectVoke, type VokeAmqpCreds } from './amqp-connect';

export interface VcpCommandInput {
  commandType: string;             // e.g. 'site-setpoint', 'mode'
  siteId: string;                  // target Voke site / plant identifier
  source: string;                  // your partner ID (e.g. 'my-esm')
  payload: Record<string, unknown>; // shape depends on commandType
  correlationId?: string;          // echoed back in event.status for correlation
}

export async function publishCommand(creds: VokeAmqpCreds, input: VcpCommandInput) {
  const { ch, exchange, conn } = await connectVoke(creds);

  const envelope = {
    version: '1.1' as const,
    messageId: randomUUID(),
    correlationId: input.correlationId,
    timestamp: new Date().toISOString(),
    source: input.source,
    siteId: input.siteId,
    payload: input.payload,
  };

  const routingKey = `${creds.orgSlug}.command.${input.commandType}`;
  ch.publish(exchange, routingKey, Buffer.from(JSON.stringify(envelope)), {
    contentType: 'application/json',
    persistent: true,
    messageId: envelope.messageId,
    correlationId: envelope.correlationId,
    timestamp: Date.parse(envelope.timestamp),
  });

  await ch.close();
  await conn.close();
}

Poté vytvořte run-publish.ts pro odeslání příkazu site-setpoint na vaši fake plant:

// run-publish.ts
import { publishCommand } from './publish-command';

(async () => {
  await publishCommand(
    {
      amqpsUri: 'amqps://YOUR-ORG-SLUG:URL-ENCODED-KEY@amqp.voke.turena.cz:5671/partner-XXXX',
      orgSlug: 'YOUR-ORG-SLUG',
    },
    {
      commandType: 'site-setpoint',
      siteId: 'YOUR-FAKE-PLANT-EXTERNAL-ID',
      source: 'YOUR-PARTNER-ID',
      payload: {
        type: 'POWER',
        targetValueKw: 10,
        direction: 'EXPORT',
        includeConsumption: true,
        priority: 'NORMAL',
        validFrom: new Date().toISOString(),
      },
    },
  );
  console.log('command published');
})();
npx tsx run-publish.ts
# Expected: command published

Zkontrolujte Voke admin pod Plants → [vaše plant] → Commands a uvidíte příkaz zalogovaný se stavem EXECUTED nebo REJECTED (rejected znamená, že fake plant zatím není napojena na skutečnou Voke plant — pro tento krok je to v pořádku).

Krok 6: Přidejte konzumenta telemetrie

Vytvořte consume-telemetry.ts. Voke publikuje telemetrické události do vcp.{slug}.event.telemetry s routing keys {slug}.event.telemetry.realtime.{siteId} a {slug}.event.telemetry.meter.{siteId}. Výchozí fronta je svázána (bound) s #, takže přijímá každou site; spárujte segment typu routing key (.realtime. / .meter.) namísto přesného suffixu. Každá zpráva je VCP envelope obsahující telemetrický payload.

// consume-telemetry.ts
// Subscribe to telemetry events emitted by Voke on your org's outbound
// event.telemetry queue.
import { connectVoke, type VokeAmqpCreds } from './amqp-connect';

export interface VcpEnvelope<T = unknown> {
  version: '1.1';
  messageId: string;
  correlationId?: string;
  timestamp: string;
  source: string;
  siteId: string;
  payload: T;
}

export async function consumeTelemetry(
  creds: VokeAmqpCreds,
  onMessage: (envelope: VcpEnvelope) => void,
) {
  const { ch, outbound } = await connectVoke(creds);
  await ch.consume(outbound.telemetry, (msg) => {
    if (!msg) return;
    try {
      const envelope = JSON.parse(msg.content.toString()) as VcpEnvelope;
      onMessage(envelope);
      ch.ack(msg);
    } catch {
      // Malformed payload — dead-letter rather than redeliver.
      ch.nack(msg, false, false);
    }
  });
}

Poté vytvořte runner, který loguje telemetrii po dobu 30 sekund:

// run-consume.ts
import { consumeTelemetry } from './consume-telemetry';

(async () => {
  console.log('Listening for telemetry for 30 seconds...');
  await consumeTelemetry(
    {
      amqpsUri: 'amqps://YOUR-ORG-SLUG:URL-ENCODED-KEY@amqp.voke.turena.cz:5671/partner-XXXX',
      orgSlug: 'YOUR-ORG-SLUG',
    },
    (envelope) => {
      console.log('telemetry siteId:', envelope.siteId);
      console.log('telemetry payload:', JSON.stringify(envelope.payload, null, 2));
    },
  );
  await new Promise((resolve) => setTimeout(resolve, 30_000));
  process.exit(0);
})();
npx tsx run-consume.ts

Telemetrické zprávy budete přijímat až poté, co Voke plant bude mít externalPlantId nastavené tak, aby odpovídalo externalPlantId fake plant, a tato plant bude publikovat živá data přes MQTT. Pro smoke test bez partnera viz PLC quick start pro simulaci plant odesílající telemetrii.

Další kroky

  • Per-org AMQP queues — kompletní sémantika front, routing keys, durability a konfigurace dead-letter.
  • VCP data model — kanonické tvary payloadů pro příkazy a telemetrické envelopy.
  • Errors & retries — error envelope, kódy zamítnutí (rejection codes), idempotence a chování při opětovném připojení.

On this page