BUILD YOUR CONNECTED WORLD

Mulai dari satu sinyal.

Hubungkan hardware yang Anda punya, terima telemetry, dan buat dashboard yang sesuai proyek Anda.

Getting started

  1. Daftar akun, lalu buat workspace.
  2. Buka Devices → Tambah perangkat. Salin Device ID dan token yang ditampilkan.
  3. Buat datastream temperature bertipe number, unit °C. Anda dapat memilih datastream awal saat membuat perangkat.
  4. Kirim payload melalui HTTP atau MQTT. Status perangkat berubah online setelah worker menyimpan telemetry.
  5. Buat dashboard, masuk ke mode edit, tambahkan Value, lalu pilih perangkat dan datastream.
  6. Klik Simpan dan Preview untuk menerima pembaruan realtime.

Anda tidak perlu membeli hardware dari AhrarTech. ESP32, ESP8266, Arduino, Raspberry Pi, gateway, atau software Anda dapat menggunakan protokol yang sama.

HTTP API

Token perangkat menentukan identitas perangkat dan workspace. Jangan mengirim workspace_id dalam payload.

curl -X POST https://HOST/api/v1/device/telemetry \
  -H 'Authorization: Bearer DEVICE_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"temperature":28.4,"humidity":71}'

Respons sukses menandakan pesan diterima antrean. Nilai tampil setelah diproses worker.

{"data":{"success":true},"meta":{"request_id":"..."}}

URL di atas otomatis memakai host tempat Anda membuka aplikasi. Gunakan HTTPS ketika perangkat mengakses melalui internet.

MQTT

PengaturanNilai
Host / portHOST : 1883
UsernameDevice ID
PasswordDevice token
Publish telemetry / stateQoS 1, retain false
Subscribe commandQoS 1
ahrartech/{workspace_id}/{device_id}/telemetry
ahrartech/{workspace_id}/{device_id}/command
ahrartech/{workspace_id}/{device_id}/state

ACL broker membatasi setiap perangkat ke topic miliknya. Token lama berhenti mendapatkan izin setelah dicabut atau diganti. Gunakan MQTT melalui TLS pada production.

Connect ESP32

Dua contoh siap pakai, masing-masing satu file .ino tanpa file konfigurasi terpisah — salin, ganti bagian EDIT THESE di bagian atas (Wi-Fi, domain, Workspace ID, Device ID, Device token), lalu unggah lewat Arduino IDE.

MQTT — telemetry + command dua arah

Library: PubSubClient, ArduinoJson. Mengirim temperature/humidity/voltage tiap 3 detik, dan menerima command relay lewat topic command — status aktual dilaporkan balik lewat topic state.

// AhrarTech IoT — ESP32 + MQTT (telemetry + two-way command/control)
// Board: ESP32. Library (Arduino Library Manager): PubSubClient, ArduinoJson.
//
// Edit the block below with your own values, then upload as-is — no other
// file needed.

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <time.h>

// ============ EDIT THESE ============
const char* WIFI_SSID     = "YOUR_WIFI";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST     = "iot.ahrartech.com"; // your workspace's domain (or LAN IP for local dev)
const int   MQTT_PORT     = 8883;                // 8883 = TLS (production), 1883 = plain (local dev only)
const bool  USE_TLS       = true;
const char* ROOT_CA       = "";                  // paste the broker's CA chain here when USE_TLS is true (see below)
const char* WORKSPACE_ID  = "YOUR_WORKSPACE_UUID";
const char* DEVICE_ID     = "YOUR_DEVICE_UUID";
const char* DEVICE_TOKEN  = "YOUR_ONE_TIME_DEVICE_TOKEN"; // Devices page -> shown once when the device is created
const int   RELAY_PIN     = 2;                   // demonstrate with an LED / low-voltage test load
// =====================================
//
// To fetch ROOT_CA for TLS, run on your computer and paste the PEM output
// (including the ----BEGIN/END----- lines) between the quotes above:
//   openssl s_client -connect iot.ahrartech.com:8883 -servername iot.ahrartech.com \
//     </dev/null 2>/dev/null | openssl x509
//
// Quick start without TLS: set USE_TLS=false and MQTT_PORT=1883 — only on a
// trusted local network, never for a device you actually deploy.

WiFiClient plainClient;
WiFiClientSecure secureClient;
Client& connection = USE_TLS ? static_cast<Client&>(secureClient) : static_cast<Client&>(plainClient);
PubSubClient mqtt(connection);
String rootTopic;
char lastCommand[37] = "";
unsigned long lastTelemetry = 0, lastReconnect = 0;
bool configured = false;

void onCommand(char* topic, byte* payload, unsigned int length) {
  if (String(topic) != rootTopic + "/command" || length > 1024) return;
  JsonDocument command;
  if (deserializeJson(command, payload, length)) return;
  const char* id = command["id"] | "";
  const char* key = command["datastream"] | "";
  const time_t now = time(nullptr);
  const uint64_t expires = command["expires_at_unix"] | uint64_t(0);
  if (strlen(id) != 36 || strcmp(key, "relay") != 0 || !command["value"].is<bool>()) return;
  if (now < 1700000000 || expires < static_cast<uint64_t>(now)) return;
  if (strcmp(id, lastCommand) != 0) {
    digitalWrite(RELAY_PIN, command["value"].as<bool>() ? HIGH : LOW);
    strlcpy(lastCommand, id, sizeof(lastCommand));
  }
  JsonDocument state;
  state["command_id"] = id;
  state["values"]["relay"] = (digitalRead(RELAY_PIN) == HIGH);
  char output[256];
  serializeJson(state, output, sizeof(output));
  mqtt.publish((rootTopic + "/state").c_str(), output, false);
  // This acknowledges the GPIO level. Physical actuator feedback requires its own sensor.
}

void setup() {
  Serial.begin(115200);
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW);
  if (USE_TLS && strlen(ROOT_CA) < 100) {
    Serial.println("Install a valid broker CA certificate before enabling TLS.");
    return;
  }
  if (USE_TLS) secureClient.setCACert(ROOT_CA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  configTime(0, 0, "pool.ntp.org", "time.cloudflare.com");
  rootTopic = String("ahrartech/") + WORKSPACE_ID + "/" + DEVICE_ID;
  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setBufferSize(2048);
  mqtt.setKeepAlive(20);
  mqtt.setCallback(onCommand);
  configured = true;
}

void loop() {
  if (!configured || WiFi.status() != WL_CONNECTED) { delay(50); return; }
  if (!mqtt.connected()) {
    // EMQX renews device authentication every minute; subscribe again after reconnect.
    if (millis() - lastReconnect < 3000) return;
    lastReconnect = millis();
    if (mqtt.connect(DEVICE_ID, DEVICE_ID, DEVICE_TOKEN)) mqtt.subscribe((rootTopic + "/command").c_str(), 1);
    return;
  }
  mqtt.loop();
  if (millis() - lastTelemetry >= 3000) {
    lastTelemetry = millis();
    JsonDocument telemetry;
    // Example readings: replace these three values with actual sensor measurements.
    telemetry["temperature"] = 28.4;
    telemetry["humidity"] = 67;
    telemetry["voltage"] = 12.2;
    telemetry["relay"] = (digitalRead(RELAY_PIN) == HIGH);
    char output[256];
    serializeJson(telemetry, output, sizeof(output));
    mqtt.publish((rootTopic + "/telemetry").c_str(), output, false);
  }
}

HTTP — telemetry saja

Library: ArduinoJson. Kirim satu POST tiap 10 detik. Protokol HTTP tidak mendukung command dua arah (tak ada endpoint untuk device polling command) — kalau butuh kontrol, pakai contoh MQTT di atas.

// AhrarTech IoT — ESP32 + HTTP (telemetry only, no MQTT)
// Board: ESP32. Library (Arduino Library Manager): ArduinoJson.
//
// AhrarTech IoT command/control (two-way, e.g. a relay) REQUIRES MQTT — there
// is no HTTP endpoint for a device to poll for pending commands. This example
// is telemetry-only. For telemetry + command, see the MQTT example above.
//
// Edit the block below with your own values, then upload as-is — no other
// file needed.

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

// ============ EDIT THESE ============
const char* WIFI_SSID     = "YOUR_WIFI";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* API_HOST      = "iot.ahrartech.com";          // your workspace's domain — no scheme, no path
const char* DEVICE_TOKEN  = "YOUR_ONE_TIME_DEVICE_TOKEN"; // Devices page -> shown once when the device is created
const bool  VERIFY_TLS    = false;                        // see note below
const char* ROOT_CA       = "";
// =====================================
//
// VERIFY_TLS=false skips certificate validation — fine while testing on a
// trusted network, NOT for a device you actually deploy (anyone on the path
// could then read/replace your token and telemetry). To harden: set
// VERIFY_TLS=true and paste the server's certificate chain into ROOT_CA,
// fetched with:
//   openssl s_client -connect iot.ahrartech.com:443 -servername iot.ahrartech.com \
//     </dev/null 2>/dev/null | openssl x509

WiFiClientSecure client;
unsigned long lastSend = 0;
const unsigned long SEND_INTERVAL_MS = 10000;

void connectWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(300);
    Serial.print(".");
  }
  Serial.println(" connected, IP " + WiFi.localIP().toString());
}

void sendTelemetry() {
  HTTPClient http;
  String url = String("https://") + API_HOST + "/api/v1/device/telemetry";
  if (!http.begin(client, url)) {
    Serial.println("HTTPClient begin() failed");
    return;
  }
  http.addHeader("Content-Type", "application/json");
  http.addHeader("Authorization", String("Bearer ") + DEVICE_TOKEN);

  // Replace these two lines with actual sensor readings. Keys must match
  // datastreams you created for this device (Dashboard -> Datastreams).
  JsonDocument doc;
  doc["temperature"] = 28.4;
  doc["humidity"] = 67;
  String body;
  serializeJson(doc, body);

  int status = http.POST(body);
  if (status == 200) {
    Serial.println("Telemetry sent: " + body);
  } else {
    Serial.printf("Telemetry failed: HTTP %d - %s\n", status, http.getString().c_str());
  }
  http.end();
}

void setup() {
  Serial.begin(115200);
  connectWiFi();
  if (VERIFY_TLS) {
    client.setCACert(ROOT_CA);
  } else {
    client.setInsecure(); // quick start only — see the note above to harden
  }
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) {
    connectWiFi();
    return;
  }
  if (millis() - lastSend >= SEND_INTERVAL_MS) {
    lastSend = millis();
    sendTelemetry();
  }
}

Telemetry & historical data

Definisikan key terlebih dahulu. Tipe yang didukung: number, integer, boolean, string. Payload yang memakai key tak dikenal, tipe salah, atau nilai di luar min/max ditolak.

Data terbaru diperbarui lewat WebSocket. Grafik mengambil rentang waktu dan interval agregasi; maksimal 2.000 titik per respons. Data mentah mendukung cursor untuk halaman berikutnya.

GET /api/v1/workspaces/{workspace_id}/devices/{device_id}/telemetry
    ?datastream=temperature
    &from=2026-09-08T00:00:00Z
    &to=2026-09-08T06:00:00Z
    &resolution=1m

Interval: raw, 1m, 5m, 1h, 1d. Rentang query maksimal 31 hari; retensi bawaan 30 hari. Perangkat dianggap online selama pembacaan terakhir belum lebih dari 45 detik.

Commands & reported state

Tambahkan datastream boolean bernama relay, aktifkan writable, lalu hubungkan Switch pada dashboard. Browser mengirim command ke backend menggunakan sesi pengguna.

// Command yang diterima perangkat
{
  "id":"COMMAND_UUID",
  "datastream":"relay",
  "value":true,
  "expires_at":"2026-09-08T10:00:30Z"
}

Perangkat memeriksa expiry dan menyimpan command ID terakhir untuk mencegah aksi berulang. Setelah mengatur output, publish reported state berisi ID dan nilai aktual. Backend mengakui command jika identitas, datastream, nilai, dan waktu valid.

Status: PENDING → SENT → ACKNOWLEDGED, atau FAILED / TIMEOUT. ACKNOWLEDGED mencatat laporan perangkat; pembuktian kondisi fisik tetap memerlukan sensor umpan balik yang sesuai.

Dashboard & widgets

Tambahkan widget dari palet kiri, tarik pegangan untuk memindahkan, dan gunakan sudut untuk mengubah ukuran. Panel properties memilih data, unit, presisi, serta nilai command. Posisi dan ukuran juga bisa diketik menggunakan keyboard.

WidgetDatastream
ValueSemua tipe
Gauge / Line chartNumber atau integer
LEDBoolean
SwitchWritable boolean
SliderWritable number / integer
ButtonDatastream writable sesuai tipe nilai command
TextTanpa binding data

Dashboard private mengikuti akses workspace. Dashboard public memberikan akses baca kepada siapa pun yang memiliki tautan. Fitur kontrol tetap memerlukan login dan role yang sesuai. Simpan perubahan sebelum berbagi.