IoT

Membuat Arduino Library yang Profesional

TOKEN

Pelajari cara membuat Arduino Library dengan struktur yang benar — dari header files, class design, examples, hingga publish ke Arduino Library Manager

1. Mengapa Membuat Library Arduino?

Membuat Arduino Library memungkinkan kita membungkus kode yang kompleks ke dalam antarmuka (API) yang mudah digunakan. Library yang baik membuat proyek lebih modular, reusable, dan mudah di-maintain.

Keuntungan Library yang Baik

📄
Tanpa Library
Kode Repetitif & Berantakan
  • ❌ Copy-paste kode ke setiap proyek
  • ❌ Sulit sharing ke orang lain
  • ❌ Tidak ada dependency management
  • ❌ No syntax highlighting
📚
Dengan Library
Modular & Profesional
  • ✅ Reusable di banyak proyek
  • ✅ Install via Library Manager
  • ✅ Auto dependency resolution
  • ✅ Syntax highlighting otomatis

2. Struktur Library Arduino

Arduino mengharuskan struktur folder yang spesifik. Ada dua gaya: flat layout (sederhana) dan src layout (recommended untuk library besar).

Struktur Folder — Flat Layout
MySensorLibrary/
├── src/                       ← Source files
│   ├── MySensorLibrary.h      ← Header utama (wajib)
│   ├── MySensorLibrary.cpp    ← Implementation
│   └── utils.h                ← Helper header
├── examples/                   ← Contoh penggunaan
│   ├── BasicRead/
│   │   └── BasicRead.ino
│   └── AdvancedConfig/
│       └── AdvancedConfig.ino
├── keywords.txt                ← Syntax highlighting
├── library.properties          ← Metadata library
├── LICENSE                     ← Lisensi (MIT recommended)
└── README.md                   ← Dokumentasi
Struktur Folder — src/ Layout (Recommended)
MySensorLibrary/
├── src/
│   ├── MySensorLibrary.h          ← Main header (include semua)
│   ├── MySensorLibrary.cpp         ← Core implementation
│   ├── sensor_driver.h             ← Sensor-specific header
│   ├── sensor_driver.cpp           ← Sensor-specific impl
│   ├── utils/
│   │   ├── crc.h
│   │   └── crc.cpp
│   └── config.h                    ← Konfigurasi library
├── examples/
│   ├── SimpleRead/
│   │   └── SimpleRead.ino
│   ├── MultipleSensors/
│   │   └── MultipleSensors.ino
│   └── Calibration/
│       └── Calibration.ino
├── keywords.txt
├── library.properties
├── LICENSE
└── README.md

3. Header File (.h) yang Benar

Header file adalah "wajah" library yang dilihat user. Pastikan menggunakan include guards atau #pragma once untuk mencegah double inclusion.

C++ — MySensorLibrary.h
/**
 * @file MySensorLibrary.h
 * @brief Library untuk membaca sensor suhu DHT dan BMP
 *
 * Library ini menyediakan API sederhana untuk membaca
 * sensor suhu dan kelembaban dari DHT22 dan BMP280.
 *
 * @author BeebaneLabs
 * @version 1.0.0
 * @date 2026-06-29
 * @license MIT
 */

#ifndef MY_SENSOR_LIBRARY_H
#define MY_SENSOR_LIBRARY_H

#include <Arduino.h>
#include <Wire.h>

// Version info
#define MY_SENSOR_LIB_VERSION "1.0.0"
#define MY_SENSOR_LIB_VERSION_MAJOR 1
#define MY_SENSOR_LIB_VERSION_MINOR 0
#define MY_SENSOR_LIB_VERSION_PATCH 0

// Sensor types yang didukung
enum SensorType {
  SENSOR_DHT22 = 0,
  SENSOR_BMP280,
  SENSOR_SHT31,
  SENSOR_COUNT
};

// Struct untuk data sensor
struct SensorData {
  float temperature;    // °C
  float humidity;       // %RH (hanya untuk sensor dengan humidity)
  float pressure;       // hPa (hanya untuk sensor dengan tekanan)
  float altitude;       // meter (dihitung dari pressure)
  bool isValid;         // true jika data valid
  unsigned long timestamp;  // millis() saat pembacaan
};

// Callback type untuk data baru
typedef void (*DataCallback)(SensorData data);

/**
 * @class MySensor
 * @brief Class utama untuk interaksi dengan sensor
 *
 * Contoh penggunaan:
 * @code
 *   MySensor sensor;
 *   sensor.begin(SENSOR_DHT22, 4);
 *   SensorData data = sensor.read();
 *   Serial.println(data.temperature);
 * @endcode
 */
class MySensor {
public:
    /**
     * @brief Constructor default
     */
    MySensor();

    /**
     * @brief Destructor — cleanup resources
     */
    ~MySensor();

    /**
     * @brief Inisialisasi sensor
     * @param type Tipe sensor (SENSOR_DHT22, SENSOR_BMP280, dll)
     * @param pin GPIO pin (untuk DHT) atau I2C address (untuk I2C sensor)
     * @param wire Pointer ke TwoWire (default: &Wire)
     * @return true jika inisialisasi berhasil
     */
    bool begin(SensorType type, uint8_t pin, TwoWire *wire = &Wire);

    /**
     * @brief Baca data sensor
     * @return SensorData struct berisi pembacaan
     */
    SensorData read();

    /**
     * @brief Baca hanya suhu
     * @return Suhu dalam °C, atau NAN jika error
     */
    float readTemperature();

    /**
     * @brief Baca hanya kelembaban
     * @return Kelembaban dalam %RH, atau NAN jika error
     */
    float readHumidity();

    /**
     * @brief Set callback saat data baru tersedia
     * @param callback Fungsi yang dipanggil saat data ready
     */
    void onDataReady(DataCallback callback);

    /**
     * @brief Mulai pembacaan periodik di background
     * @param intervalMs Interval pembacaan dalam milidetik
     */
    void startAutoRead(uint32_t intervalMs = 2000);

    /**
     * @brief Stop pembacaan periodik
     */
    void stopAutoRead();

    /**
     * @brief Set offset kalibrasi
     * @param tempOffset Offset suhu dalam °C
     * @param humidOffset Offset kelembaban dalam %RH
     */
    void setCalibration(float tempOffset, float humidOffset);

    /**
     * @brief Cek apakah sensor terhubung
     * @return true jika sensor responsif
     */
    bool isConnected();

    /**
     * @brief Dapatkan informasi sensor
     * @return String berisi info sensor
     */
    String getInfo();

    /**
     * @brief Dapatkan library version
     * @return Versi library dalam format "x.y.z"
     */
    static const char* getVersion() { return MY_SENSOR_LIB_VERSION; }

private:
    SensorType _type;
    uint8_t _pin;
    TwoWire *_wire;
    bool _initialized;
    float _tempOffset;
    float _humidOffset;
    DataCallback _callback;
    SensorData _lastData;

    // Private methods
    bool _initDHT22();
    bool _initBMP280();
    SensorData _readDHT22();
    SensorData _readBMP280();
    float _calculateAltitude(float pressure);
};

#endif // MY_SENSOR_LIBRARY_H
💡 Include Guards vs #pragma once

Gunakan #ifndef/#define/#endif (include guards) untuk kompatibilitas lintas compiler. #pragma once lebih praktis tapi tidak dijamin didukung semua compiler (meskipun hampir semua compiler modern mendukungnya). Untuk Arduino library, include guards lebih aman.

4. Class Design & OOP

Arduino library yang baik menggunakan prinsip OOP: encapsulation (private members), abstraction (public API sederhana), dan clean interface.

Prinsip Class Design

PrinsipPraktikContoh
EncapsulationPrivate members dengan _ prefixuint8_t _pin;
ConstructorInisialisasi semua memberMySensor() : _initialized(false) {}
begin() patternPisahkan konstruksi dari inisialisasibool begin(type, pin);
Error handlingReturn bool/nullptr untuk errorif (!sensor.begin()) return;
Default paramsParameter opsional dengan defaultbegin(type, pin, &Wire);
Const correctnessTandai method yang tidak modifyfloat readTemp() const;
⚠️ Jangan Inisialisasi Hardware di Constructor!

Global objects di Arduino diinisialisasi sebelum setup() — sebelum hardware siap. Gunakan begin() pattern: constructor hanya set nilai default, begin() yang inisialisasi hardware (GPIO, I2C, SPI).

5. Implementation File (.cpp)

C++ — MySensorLibrary.cpp
/**
 * @file MySensorLibrary.cpp
 * @brief Implementation dari MySensor class
 */

#include "MySensorLibrary.h"

// BMP280 I2C address
#define BMP280_ADDR 0x76

// ============ Constructor & Destructor ============

MySensor::MySensor()
    : _type(SENSOR_DHT22)
    , _pin(0)
    , _wire(&Wire)
    , _initialized(false)
    , _tempOffset(0.0)
    , _humidOffset(0.0)
    , _callback(nullptr)
{
    memset(&_lastData, 0, sizeof(SensorData));
}

MySensor::~MySensor() {
    stopAutoRead();
}

// ============ Public Methods ============

bool MySensor::begin(SensorType type, uint8_t pin, TwoWire *wire) {
    _type = type;
    _pin = pin;
    _wire = wire ? wire : &Wire;

    switch (_type) {
        case SENSOR_DHT22:
            _initialized = _initDHT22();
            break;
        case SENSOR_BMP280:
            _wire->begin();
            _initialized = _initBMP280();
            break;
        default:
            Serial.println("[MySensor] Unknown sensor type!");
            _initialized = false;
            break;
    }

    if (_initialized) {
        Serial.printf("[MySensor] Initialized: type=%d, pin=%d\n", _type, _pin);
    }

    return _initialized;
}

SensorData MySensor::read() {
    if (!_initialized) {
        _lastData.isValid = false;
        return _lastData;
    }

    switch (_type) {
        case SENSOR_DHT22:
            _lastData = _readDHT22();
            break;
        case SENSOR_BMP280:
            _lastData = _readBMP280();
            break;
        default:
            _lastData.isValid = false;
            break;
    }

    // Apply calibration
    if (_lastData.isValid) {
        _lastData.temperature += _tempOffset;
        _lastData.humidity += _humidOffset;
        _lastData.timestamp = millis();
    }

    // Trigger callback
    if (_callback && _lastData.isValid) {
        _callback(_lastData);
    }

    return _lastData;
}

float MySensor::readTemperature() {
    SensorData data = read();
    return data.isValid ? data.temperature : NAN;
}

float MySensor::readHumidity() {
    SensorData data = read();
    return data.isValid ? data.humidity : NAN;
}

void MySensor::onDataReady(DataCallback callback) {
    _callback = callback;
}

void MySensor::setCalibration(float tempOffset, float humidOffset) {
    _tempOffset = tempOffset;
    _humidOffset = humidOffset;
}

bool MySensor::isConnected() {
    if (_type == SENSOR_BMP280) {
        _wire->beginTransmission(BMP280_ADDR);
        return (_wire->endTransmission() == 0);
    }
    return _initialized;
}

String MySensor::getInfo() {
    String info = "MySensor v" + String(MY_SENSOR_LIB_VERSION);
    info += " | Type: " + String(_type);
    info += " | Pin: " + String(_pin);
    info += " | Init: " + String(_initialized ? "YES" : "NO");
    return info;
}

// ============ Private Methods ============

bool MySensor::_initDHT22() {
    // DHT22 library handles init internally
    pinMode(_pin, INPUT_PULLUP);
    delay(2000);  // DHT22 warm-up time
    return true;
}

bool MySensor::_initBMP280() {
    _wire->beginTransmission(BMP280_ADDR);
    if (_wire->endTransmission() != 0) return false;

    // Read chip ID
    _wire->beginTransmission(BMP280_ADDR);
    _wire->write(0xD0);  // Chip ID register
    _wire->endTransmission();
    _wire->requestFrom(BMP280_ADDR, (uint8_t)1);
    uint8_t chipId = _wire->read();

    if (chipId != 0x58) {
        Serial.printf("[MySensor] BMP280 ID mismatch: 0x%02X\n", chipId);
        return false;
    }

    // Configure: oversampling x16, normal mode
    _wire->beginTransmission(BMP280_ADDR);
    _wire->write(0xF4);  // ctrl_meas
    _wire->write(0xB7);
    _wire->endTransmission();

    return true;
}

SensorData MySensor::_readDHT22() {
    SensorData data;
    // Simplified DHT22 read — in real code, use DHT library
    data.temperature = 25.0;  // Placeholder
    data.humidity = 60.0;
    data.pressure = 0;
    data.altitude = 0;
    data.isValid = true;
    return data;
}

SensorData MySensor::_readBMP280() {
    SensorData data = {0};

    uint8_t buf[6];
    _wire->beginTransmission(BMP280_ADDR);
    _wire->write(0xF7);  // Start register
    _wire->endTransmission();
    _wire->requestFrom(BMP280_ADDR, (uint8_t)6);

    for (int i = 0; i < 6; i++) {
        buf[i] = _wire->read();
    }

    // Parse raw data
    int32_t rawPress = ((int32_t)buf[0] << 12) | ((int32_t)buf[1] << 4) | (buf[2] >> 4);
    int32_t rawTemp  = ((int32_t)buf[3] << 12) | ((int32_t)buf[4] << 4) | (buf[5] >> 4);

    // Simplified calculation — real code needs calibration
    data.temperature = (float)rawTemp / 100.0;
    data.pressure = (float)rawPress / 256.0 / 100.0;
    data.altitude = _calculateAltitude(data.pressure);
    data.humidity = 0;
    data.isValid = true;

    return data;
}

float MySensor::_calculateAltitude(float pressure) {
    // Barometric formula
    return 44330.0 * (1.0 - pow(pressure / 1013.25, 0.1903));
}

6. keywords.txt: Syntax Highlighting

File keywords.txt memberitahu Arduino IDE cara me-highlight keyword library di editor. Format: keyword diikuti TAB lalu tipe KEYWORD1/KEYWORD2/LITERAL1.

keywords.txt
#######################################
# Syntax Coloring Map untuk MySensorLibrary
#######################################

#######################################
# Class & Datatypes (KEYWORD1 — cyan/ungu)
#######################################
MySensor	KEYWORD1
SensorData	KEYWORD1
SensorType	KEYWORD1
DataCallback	KEYWORD1

#######################################
# Methods & Functions (KEYWORD2 — orange)
#######################################
begin	KEYWORD2
read	KEYWORD2
readTemperature	KEYWORD2
readHumidity	KEYWORD2
onDataReady	KEYWORD2
startAutoRead	KEYWORD2
stopAutoRead	KEYWORD2
setCalibration	KEYWORD2
isConnected	KEYWORD2
getInfo	KEYWORD2
getVersion	KEYWORD2

#######################################
# Constants (LITERAL1 — hijau)
#######################################
SENSOR_DHT22	LITERAL1
SENSOR_BMP280	LITERAL1
SENSOR_SHT31	LITERAL1
MY_SENSOR_LIB_VERSION	LITERAL1
⚠️ Penting: Gunakan TAB, bukan Spasi!

Arduino IDE membutuhkan TAB character (bukan spasi) antara keyword dan tipe. Jika syntax highlighting tidak bekerja, kemungkinan besar kamu menggunakan spasi. Gunakan text editor yang bisa menunjukkan whitespace characters.

7. library.properties: Metadata

File library.properties berisi metadata yang digunakan Arduino Library Manager untuk menampilkan informasi library.

INI — library.properties
name=MySensorLibrary
version=1.0.0
author=BeebaneLabs <beebane@example.com>
maintainer=BeebaneLabs <beebane@example.com>
sentence=Universal sensor library for DHT22, BMP280, SHT31.
paragraph=Read temperature, humidity, and pressure from popular I2C sensors. Supports auto-calibration, callback-based reading, and multi-sensor configuration.
category=Sensors
url=https://github.com/Beebane25/MySensorLibrary
architectures=*
depends=Wire
includes=MySensorLibrary.h
depends=Adafruit BMP280 Library

Field yang Wajib

FieldWajib?Deskripsi
name✅Nama library (tanpa spasi untuk Library Manager)
version✅SemVer (x.y.z)
author✅Nama dan email
maintainer✅Nama dan email maintainer
sentence✅Satu kalimat deskripsi (max 90 char)
paragraph✅Deskripsi lebih panjang
category✅Communication, Display, Sensors, Signal Input/Output, dll
url✅URL repository GitHub
architectures✅* untuk semua, esp32, avr, dll

8. Examples yang Baik

Contoh yang baik adalah dokumen terpenting dari sebuah library. Letakkan di folder examples/ dengan nama folder = nama .ino file.

C++ — examples/BasicRead/BasicRead.ino
/**
 * BasicRead.ino
 * Contoh paling sederhana membaca sensor
 *
 * Wiring:
 *   DHT22 VCC  → 3.3V
 *   DHT22 DATA → GPIO 4 (10kΩ pull-up)
 *   DHT22 GND  → GND
 */

#include <MySensorLibrary.h>

MySensor sensor;

void setup() {
  Serial.begin(115200);
  Serial.println("=== MySensor Basic Read ===");

  // Inisialisasi sensor DHT22 pada pin 4
  if (!sensor.begin(SENSOR_DHT22, 4)) {
    Serial.println("ERROR: Sensor tidak terdeteksi!");
    while (1) {
      delay(1000);  // Stop di sini
    }
  }

  Serial.println("Sensor OK!");
  Serial.println(sensor.getInfo());
}

void loop() {
  // Baca semua data
  SensorData data = sensor.read();

  if (data.isValid) {
    Serial.printf("Suhu: %.1f°C | Humid: %.1f%%\n",
      data.temperature, data.humidity);
  } else {
    Serial.println("Pembacaan gagal!");
  }

  delay(2000);
}

9. Publish ke Arduino Library Manager

Langkah Publish

  1. Buat repository GitHub dengan nama yang sesuai name di library.properties
  2. Push semua file ke repository
  3. Buat tag release dengan versi yang sesuai: git tag 1.0.0 && git push --tags
  4. Register di Arduino Library Registry: Buka https://github.com/arduino/library-registry
  5. Submit issue dengan format: https://github.com/username/repo
  6. Tunggu Arduino Bot melakukan review otomatis
  7. Fix issues yang ditemukan bot (jika ada)
  8. Library akan tersedia di Arduino Library Manager setelah disetujui
💡 Library Lint Manager

Arduino Bot akan mengecek: library.properties valid, ada minimal 1 example, name unik, dan architectures terisi. Gunakan arduino-lint untuk mengecek library secara lokal sebelum submit: arduino-lint --library-manager .

10. Quiz Pemahaman

Pertanyaan 1: Mengapa hardware inisialisasi tidak dilakukan di constructor?

a) Constructor tidak bisa memanggil fungsi
b) Global objects diinisialisasi sebelum setup(), hardware belum siap
c) Lebih cepat di begin()
d) Arduino IDE melarangnya

Pertanyaan 2: Di keywords.txt, KEYWORD1 digunakan untuk apa?

a) Methods/functions
b) Class names dan data types
c) Constants
d) Comments

Pertanyaan 3: Pemisah dalam keywords.txt harus menggunakan?

a) Spasi
b) Koma
c) TAB character
d) Titik dua (colon)

Pertanyaan 4: File apa yang berisi metadata library untuk Library Manager?

a) config.json
b) package.json
c) library.properties
d) manifest.xml

Pertanyaan 5: Include guards mencegah apa?

a) Library dipakai di platform lain
b) Header file di-include dua kali (double inclusion)
c) Code injection
d) Buffer overflow
← Sebelumnya ESP32 Matter Selanjutnya → RPi Kubernetes
🔍 Zoom
100%
🎨 Tema