HomePulse

Integrationen

ioBroker einrichten

Ein lokales JavaScript sendet Heartbeat und normalisierte Objektzustände.

Voraussetzungen

  • ioBroker mit JavaScript-Adapter ab Version 7.9.0
  • HomePulse-Standort mit ioBroker-Integration
  • Standort-ID und API-Key

Schritt für Schritt

  1. Erzeuge die ioBroker-Integration in HomePulse. Direkt danach wird der vollständige Connector mit eingesetzter Standort-ID und API-Key einmalig angezeigt.
  2. Erstelle im JavaScript-Adapter ein neues Skript und füge den gesamten Connector-Code ein.
  3. Die Standardkonfiguration überwacht Admin-Verbindung, Update-Status, letzte Update-Prüfung und nur die Anzahl aktiver Socket-Clients.
  4. Ergänze bei Bedarf bewusst ausgewählte Datenpunkte in additionalStates.
  5. Starte das Skript und prüfe die Logeinträge ohne Ausgabe des Keys.
  6. Kontrolliere Heartbeat und Geräte unter HomePulse.

Beispielkonfiguration

// HomePulse Connector für ioBroker JavaScript-Adapter
// Benötigt ioBroker JavaScript-Adapter >= 7.9.0 (native Funktion httpPost).
const HOMEPULSE_URL = 'https://www.homepulse.at';
const SITE_ID = 'DEINE-STANDORT-GUID';
const API_KEY = 'hp_live_DEIN_EINMALIGER_KEY';
const CONNECTOR_VERSION = '1.1.0';
const CONNECTOR_STARTED_AT = Date.now();

const isTrue = value => value === true || value === 'true';
const connectedClientCount = value => {
  if (Array.isArray(value)) return value.length;
  const match = String(value || '').match(/^\[(\d+)\]/);
  return match ? Number(match[1]) : 0;
};
const standardStates = [
  {
    id: 'admin.0.info.connection',
    name: 'ioBroker Admin HTTP-Server',
    type: 'service',
    critical: true,
    status: value => isTrue(value) ? 'online' : 'offline',
    diagnostic: value => isTrue(value) ? 'HTTP-Server gestartet' : 'HTTP-Server nicht gestartet'
  },
  {
    id: 'admin.0.info.newUpdates',
    name: 'ioBroker Adapter-Updates',
    type: 'service',
    critical: false,
    status: value => isTrue(value) ? 'warning' : 'online',
    diagnostic: value => isTrue(value) ? 'Neue Adapter-Updates verfügbar' : 'Keine neuen Adapter-Updates gemeldet'
  },
  {
    id: 'admin.0.info.updatesNumber',
    name: 'Verfügbare ioBroker Updates',
    type: 'service',
    critical: false,
    status: value => Number(value || 0) > 0 ? 'warning' : 'online',
    diagnostic: value => `${Number(value || 0)} verfügbare Updates`
  },
  {
    id: 'admin.0.info.lastUpdateCheck',
    name: 'Letzte ioBroker Update-Prüfung',
    type: 'sensor',
    critical: false,
    status: value => value ? 'online' : 'unknown',
    diagnostic: value => value ? `Letzte Prüfung: ${String(value)}` : 'Noch keine Update-Prüfung gemeldet'
  },
  {
    id: 'admin.0.info.connected',
    name: 'ioBroker Admin-Verbindungen',
    type: 'service',
    critical: false,
    status: value => value !== null && value !== undefined ? 'online' : 'unknown',
    diagnostic: value => `Aktive Socket-Clients: ${connectedClientCount(value)}`
  }
];

// Hier nur bewusst ausgewählte Healthcheck-Datenpunkte ergänzen.
const additionalStates = [
  // {
  //   id: 'zigbee.0.sensor_bedroom.available',
  //   name: 'Zigbee Sensor Schlafzimmer',
  //   type: 'sensor',
  //   critical: false,
  //   status: value => isTrue(value) ? 'online' : 'offline',
  //   diagnostic: value => isTrue(value) ? 'Erreichbar' : 'Nicht erreichbar'
  // }
];

function sourceVersion() {
  const adminObject = getObject('system.adapter.admin.0');
  const adminVersion = adminObject && adminObject.common && adminObject.common.version;
  return adminVersion ? `admin-${adminVersion} / connector-${CONNECTOR_VERSION}` : `connector-${CONNECTOR_VERSION}`;
}

function stateTimestamp(state) {
  const timestamp = state && Number(state.ts);
  return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : null;
}

function createItem(definition) {
  const state = getState(definition.id);
  if (!state) {
    return {
      externalId: definition.id,
      name: definition.name,
      type: definition.type,
      status: 'unknown',
      lastSeenUtc: null,
      diagnostic: 'Datenpunkt in ioBroker nicht gefunden',
      isCritical: definition.critical
    };
  }

  return {
    externalId: definition.id,
    name: definition.name,
    type: definition.type,
    status: definition.status(state.val),
    lastSeenUtc: stateTimestamp(state),
    diagnostic: definition.diagnostic(state.val),
    isCritical: definition.critical
  };
}

async function post(path, body) {
  return new Promise((resolve, reject) => {
    httpPost(
      `${HOMEPULSE_URL}/api/ingest/iobroker/${path}`,
      body,
      {
        timeout: 15000,
        headers: {
          'Content-Type': 'application/json',
          'X-HomePulse-Key': API_KEY,
          'Idempotency-Key': `iob-${path}-${Date.now()}`
        }
      },
      (error, response) => {
        if (error) {
          reject(new Error(`HomePulse ${path}: ${error.message || error}`));
          return;
        }
        if (!response || response.statusCode < 200 || response.statusCode >= 300) {
          reject(new Error(`HomePulse ${path}: HTTP ${response ? response.statusCode : 'ohne Antwort'}`));
          return;
        }

        try {
          const data = typeof response.data === 'string' ? JSON.parse(response.data) : response.data;
          resolve(data || { status: 'accepted' });
        } catch {
          resolve({ status: 'accepted' });
        }
      }
    );
  });
}

async function heartbeat() {
  return post('heartbeat', {
    apiVersion: '2026-08-01',
    siteId: SITE_ID,
    source: 'iobroker',
    sourceVersion: sourceVersion(),
    sentAtUtc: new Date().toISOString(),
    status: 'online',
    uptimeSeconds: Math.floor((Date.now() - CONNECTOR_STARTED_AT) / 1000)
  });
}

async function snapshot() {
  const items = [...standardStates, ...additionalStates].map(createItem);
  return post('snapshot', {
    apiVersion: '2026-08-01',
    siteId: SITE_ID,
    source: 'iobroker',
    sourceVersion: sourceVersion(),
    sentAtUtc: new Date().toISOString(),
    eventId: `iob-snapshot-${Date.now()}`,
    isFullSnapshot: true,
    items
  });
}

async function run(label, action) {
  try {
    const result = await action();
    log(`HomePulse ${label}: ${result && result.status ? result.status : 'accepted'}`, 'info');
  } catch (error) {
    log(`HomePulse ${label} fehlgeschlagen: ${error.message}`, 'error');
  }
}

schedule('*/1 * * * *', () => run('Heartbeat', heartbeat));
schedule('*/5 * * * *', () => run('Snapshot', snapshot));
run('Heartbeat', heartbeat);
run('Snapshot', snapshot);

Testschritt

Skript manuell starten und im ioBroker-Log ausschließlich HTTP-Status/Fehlercode kontrollieren.

Erwartetes Ergebnis

Heartbeat erscheint binnen einer Minute; die ausgewählten Zustände erscheinen unter Geräte & Entitäten.

Fehlerbehebung

  • httpPost fehlt: JavaScript-Adapter auf mindestens Version 7.9.0 aktualisieren.
  • 403/401: API-Key und Integration prüfen; bei einem offengelegten Key zuerst rotieren.
  • Falscher Status: Mapping im items-Block an die Bedeutung des Datenpunkts anpassen.

Sicherheitshinweise

  • Das Skript und den darin enthaltenen API-Key nur für Administratoren zugänglich machen.
  • newsFeed, updatesList und Namen verbundener Admin-Clients werden aus Gründen der Datenminimierung nicht übertragen.
  • Keine kompletten Objektbäume oder personenbezogenen Werte übertragen.
  • Skriptlogs dürfen Header und Body mit Key nicht ausgeben.