Kode Progam Jadwal Sholat Arduino C++

Posted on

Arduino Prayer Times Modifikasi

Saya mengajak teman-teman untuk dapat membuat jam sholat secara mandiri menggunakan Arduino. Di postingan saya memberikan kode program dasar untuk dapat menampilkan jadwal atau waktu sholat secara otomatis hanya dengan memasukkan koordinat, zona waktu, tanggal, bulan serta tahun seperti yang ditunjukkan pada gambar berikut ini:

Library

Saya mendapatkan sebuah library yang sangat bagus di Github bernama Arduino-Prayer-Times yang di posting oleh asmaklad (tidak tahu apakah nama seseorang). Library merupakan hasil konversi dari http://praytimes.org/manual/. Terima kasih prayertimes.org dan asmaklad!

Setelah saya mencoba library, saya membandingkan dengan waktu sholat KEMENAG dan memiliki selisih perbedaan. Dari itu, saya sedikit memodifikasi library Prayer Times tersebut. Link Library yang sudah di edit dapat teman-teman download dengan klik tombol download berikut:



Modifikasi Library

Setelah anda mendownload library dan memasukkan ke Arduino IDE, sekarang akan ada 2 file library tersebut yaitu PrayerTimes.cpp dan PrayerTimes.h. Saya memodifikasi file tersebut. Silahkan anda copy paste ke masing-masing file tersebut.

Modifikasi PrayerTimes.cpp

#include "Arduino.h"
#include "PrayerTimes.h"

double latitude;
double longitude;
double timezone;
double elevation;
double julian_date;

MethodConfig method_params[CalculationMethodsCount];
CalculationMethod calc_method;
JuristicMethod asr_juristic;
AdjustingMethod adjust_high_lats;
double dhuhr_minutes;

/* ---------------------- Trigonometric Functions ----------------------- */
static double dsin(double d) { return sin(deg2rad(d)); }
static double dcos(double d) { return cos(deg2rad(d)); }
static double dtan(double d) { return tan(deg2rad(d)); }
static double darcsin(double x) { return rad2deg(asin(x)); }
static double darccos(double x) { return rad2deg(acos(x)); }
static double darctan(double x) { return rad2deg(atan(x)); }
static double darctan2(double y, double x) { return rad2deg(atan2(y, x)); }
static double darccot(double x) { return rad2deg(atan(1.0 / x)); }
static double deg2rad(double d) { return d * M_PI / 180.0; }
static double rad2deg(double r) { return r * 180.0 / M_PI; }

static double fix_angle(double a)
{
	a = a - 360.0 * floor(a / 360.0);
	a = a < 0.0 ? a + 360.0 : a;
	return a;
}

static double fix_hour(double a)
{
	a = a - 24.0 * floor(a / 24.0);
	a = a < 0.0 ? a + 24.0 : a;
	return a;
}

/* ALGORITMA JULIAN DATE - MEEUS (ASTRONOMICAL ALGORITHMS) */
double get_julian_date(int year, int month, int day)
{
	// Adjust untuk Januari dan Februari
	if (month <= 2) {
		year -= 1;
		month += 12;
	}
	
	// Koreksi Gregorian calendar (setelah 15 Oktober 1582)
	int a = year / 100;
	int b = 2 - a + (a / 4);
	
	// Formula Meeus - presisi tinggi
	double jd = floor(365.25 * (year + 4716)) + 
	            floor(30.6001 * (month + 1)) + 
	            day + b - 1524.5;
	
	return jd;
}

/* POSISI MATAHARI - IMPROVED PRECISION */
DoublePair sun_position(double jd)
{
	// Julian centuries dari J2000.0
	double T = (jd - 2451545.0) / 36525.0;
	
	// Mean longitude matahari (derajat)
	double L0 = 280.46646 + 36000.76983 * T + 0.0003032 * T * T;
	L0 = fix_angle(L0);
	
	// Mean anomaly matahari (derajat)
	double M = 357.52911 + 35999.05029 * T - 0.0001537 * T * T;
	M = fix_angle(M);
	
	// Eccentricity orbit bumi
	double e = 0.016708634 - 0.000042037 * T - 0.0000001267 * T * T;
	
	// Sun's equation of center
	double C = (1.914602 - 0.004817 * T - 0.000014 * T * T) * dsin(M)
	         + (0.019993 - 0.000101 * T) * dsin(2 * M)
	         + 0.000289 * dsin(3 * M);
	
	// True longitude matahari
	double L = L0 + C;
	
	// True anomaly
	double v = M + C;
	
	// Radius vector (AU) - tidak dipakai tapi lengkap
	double R = (1.000001018 * (1 - e * e)) / (1 + e * dcos(v));
	
	// Obliquity of ecliptic (kemiringan sumbu bumi)
	double epsilon = 23.439291 - 0.0130042 * T - 0.00000164 * T * T + 0.000000504 * T * T * T;
	
	// Apparent longitude (koreksi nutasi)
	double omega = 125.04 - 1934.136 * T;
	double lambda = L - 0.00569 - 0.00478 * dsin(omega);
	
	// Declination matahari (derajat)
	double delta = darcsin(dsin(epsilon) * dsin(lambda));
	
	// Right Ascension (jam)
	double alpha = darctan2(dcos(epsilon) * dsin(lambda), dcos(lambda));
	alpha = alpha / 15.0; // konversi ke jam
	alpha = fix_hour(alpha);
	
	// Equation of time (jam)
	double EqT = (L0 / 15.0) - alpha;
	EqT = fix_hour(EqT + 12.0) - 12.0; // normalize ke ±12 jam
	if (EqT > 12.0) EqT -= 24.0;
	if (EqT < -12.0) EqT += 24.0;
	
	DoublePair result = {delta, EqT};
	return result;
}

void set_asr_method(JuristicMethod method_id)
{
	asr_juristic = method_id;
}

void set_high_lats_adjust_method(AdjustingMethod method_id)
{
	adjust_high_lats = method_id;
}

double sun_declination(double jd)
{
	return sun_position(jd).first;
}

double equation_of_time(double jd)
{
	return sun_position(jd).second;
}

double compute_mid_day(double _t)
{
	double t = equation_of_time(julian_date + _t);
	double z = fix_hour(12 - t);
	return z;
}

double compute_time(double g, double t)
{
	double d = sun_declination(julian_date + t);
	double z = compute_mid_day(t);
	double v = 1.0 / 15.0 * darccos((-dsin(g) - dsin(d) * dsin(latitude)) / (dcos(d) * dcos(latitude)));
	return z + (g > 90.0 ? -v : v);
}

double compute_asr(int step, double t)
{
	double d = sun_declination(julian_date + t);
	double g = -darccot(step + dtan(fabs(latitude - d)));
	return compute_time(g, t);
}

void compute_times(double times[])
{
	day_portion(times);
	
	times[Fajr]    = compute_time(180.0 - method_params[calc_method].fajr_angle, times[Fajr]);
	times[Sunrise] = compute_time(180.0 - 0.833, times[Sunrise]);
	times[Dhuhr]   = compute_mid_day(times[Dhuhr]);
	times[Asr]     = compute_asr(1 + asr_juristic, times[Asr]);
	times[Sunset]  = compute_time(0.833, times[Sunset]);
	times[Maghrib] = compute_time(method_params[calc_method].maghrib_value, times[Maghrib]);
	times[Isha]    = compute_time(method_params[calc_method].isha_value, times[Isha]);
	
	// Koreksi elevasi (dip angle)
	if (elevation > 0) {
		// Formula: correction = 1.76 * sqrt(h) arcminutes
		// Convert ke jam: arcminutes / 60 / 60
		double elev_corr = (1.76 * sqrt(elevation)) / 3600.0;
		
		times[Sunrise] -= elev_corr;  // Sunrise lebih awal
		times[Sunset]  += elev_corr;  // Sunset lebih lambat
		times[Maghrib] += elev_corr;  // Maghrib ikut Sunset
	}
}

void compute_day_times(double times[])
{
	double default_times[] = {5, 6, 12, 13, 18, 18, 18};
	
	for (int i = 0; i < TimesCount; ++i)
		times[i] = default_times[i];
	
	for (int i = 0; i < NUM_ITERATIONS; ++i)
		compute_times(times);
	
	adjust_times(times);
}

static double time_diff(double time1, double time2)
{
	return fix_hour(time2 - time1);
}

double night_portion(double angle)
{
	switch (adjust_high_lats)
	{
		case AngleBased:  return angle / 60.0;
		case MidNight:    return 1.0 / 2.0;
		case OneSeventh:  return 1.0 / 7.0;
		default:          return 0;
	}
}

void adjust_high_lat_times(double times[])
{
	double night_time = time_diff(times[Sunset], times[Sunrise]);
	
	// Adjust Fajr
	double fajr_diff = night_portion(method_params[calc_method].fajr_angle) * night_time;
	if (isnan(times[Fajr]) || time_diff(times[Fajr], times[Sunrise]) > fajr_diff)
		times[Fajr] = times[Sunrise] - fajr_diff;
	
	// Adjust Isha
	double isha_angle = method_params[calc_method].isha_is_minutes ? 18.0 : method_params[calc_method].isha_value;
	double isha_diff = night_portion(isha_angle) * night_time;
	if (isnan(times[Isha]) || time_diff(times[Sunset], times[Isha]) > isha_diff)
		times[Isha] = times[Sunset] + isha_diff;
	
	// Adjust Maghrib
	double maghrib_angle = method_params[calc_method].maghrib_is_minutes ? 4.0 : method_params[calc_method].maghrib_value;
	double maghrib_diff = night_portion(maghrib_angle) * night_time;
	if (isnan(times[Maghrib]) || time_diff(times[Sunset], times[Maghrib]) > maghrib_diff)
		times[Maghrib] = times[Sunset] + maghrib_diff;
}

void adjust_times(double times[])
{
	for (int i = 0; i < TimesCount; ++i)
		times[i] += timezone - longitude / 15.0;
	
	times[Dhuhr] += dhuhr_minutes / 60.0;
	
	if (method_params[calc_method].maghrib_is_minutes)
		times[Maghrib] = times[Sunset] + method_params[calc_method].maghrib_value / 60.0;
	
	if (method_params[calc_method].isha_is_minutes)
		times[Isha] = times[Maghrib] + method_params[calc_method].isha_value / 60.0;
	
	if (adjust_high_lats != None)
		adjust_high_lat_times(times);
}

void day_portion(double times[])
{
	for (int i = 0; i < TimesCount; ++i)
		times[i] /= 24.0;
}

void get_prayer_times(int year, int month, int day, 
                      double _latitude, double _longitude, 
                      double _timezone,
                      double _elevation,
                      double times[])
{
	latitude = _latitude;
	longitude = _longitude;
	timezone = _timezone;
	elevation = _elevation;
	
	julian_date = get_julian_date(year, month, day) - longitude / (15.0 * 24.0);
	compute_day_times(times);
}

void set_calc_method(CalculationMethod method_id)
{
	calc_method = method_id;
}

void set_fajr_angle(double angle)
{
	method_params[Custom].fajr_angle = angle;
	calc_method = Custom;
}

void set_maghrib_angle(double angle)
{
	method_params[Custom].maghrib_is_minutes = false;
	method_params[Custom].maghrib_value = angle;
	calc_method = Custom;
}

void set_isha_angle(double angle)
{
	method_params[Custom].isha_is_minutes = false;
	method_params[Custom].isha_value = angle;
	calc_method = Custom;
}

void set_dhuhr_minutes(double minutes)
{
	dhuhr_minutes = minutes;
}

void set_maghrib_minutes(double minutes)
{
	method_params[Custom].maghrib_is_minutes = true;
	method_params[Custom].maghrib_value = minutes;
	calc_method = Custom;
}

void set_isha_minutes(double minutes)
{
	method_params[Custom].isha_is_minutes = true;
	method_params[Custom].isha_value = minutes;
	calc_method = Custom;
}

void get_float_time_parts(double time, int& hours, int& minutes)
{
	time = fix_hour(time + 0.5 / 60.0);
	hours = floor(time);
	minutes = floor((time - hours) * 60.0);
}

Modifikasi PrayerTimes.h

#ifndef PrayerTimes_h
#define PrayerTimes_h
#include "Arduino.h"
#include <math.h>
#include <String.h>
#include <WString.h>
#include <stdio.h>
       
static const int NUM_ITERATIONS = 1;		// number of iterations needed to compute times

/* ------------------------------------------------------------------------------------------------- */
static const char* TimeName[] =
{
	"Fajr",
	"Sunrise",
	"Dhuhr",
	"Asr",
	"Sunset",
	"Maghrib",
	"Isha",
	"TimesCount"
};

// Calculation Methods
enum CalculationMethod
{
	Jafari, 	// Ithna Ashari
	Karachi,	// University of Islamic Sciences, Karachi
	ISNA,   	// Islamic Society of North America (ISNA)
	MWL,    	// Muslim World League (MWL)
	Makkah, 	// Umm al-Qura, Makkah
	Egypt,  	// Egyptian General Authority of Survey
	Custom, 	// Custom Setting
	CalculationMethodsCount
};

// Juristic Methods
enum JuristicMethod
{
	Shafii,    // Shafii (standard)
	Hanafi,    // Hanafi
};

// Adjusting Methods for Higher Latitudes
enum AdjustingMethod
{
	None,      	// No adjustment
	MidNight,  	// middle of night
	OneSeventh,	// 1/7th of night
	AngleBased,	// angle/60th of night
};

// Time IDs
enum TimeID
{
	Fajr,
	Sunrise,
	Dhuhr,
	Asr,
	Sunset,
	Maghrib,
	Isha,
	TimesCount
};

struct MethodConfig
{
	MethodConfig()
	{
	}
	MethodConfig(double fajr_angle,
			bool maghrib_is_minutes,
			double maghrib_value,
			bool isha_is_minutes,
			double isha_value)
	: fajr_angle(fajr_angle)
	, maghrib_is_minutes(maghrib_is_minutes)
	, maghrib_value(maghrib_value)
	, isha_is_minutes(isha_is_minutes)
	, isha_value(isha_value)
	{
	}
	double fajr_angle;
	bool   maghrib_is_minutes;
	double maghrib_value;		// angle or minutes
	bool   isha_is_minutes;
	double isha_value;		// angle or minutes
};       

struct DoublePair {
	double  first;
	double  second;
};
        	
void compute_times(double times[]);
void adjust_times(double times[]);
void day_portion(double times[]);
void set_calc_method(CalculationMethod method_id);
void adjust_high_lat_times(double times[]);
void set_asr_method(JuristicMethod method_id);
void set_high_lats_adjust_method(AdjustingMethod method_id);
void set_calc_method(CalculationMethod method_id);
void set_fajr_angle(double angle);
void set_maghrib_angle(double angle);
void set_isha_angle(double angle);

double compute_time(double g, double t);    
double sun_declination(double jd);
double compute_mid_day(double _t);
double equation_of_time(double jd);        
double night_portion(double angle);

static double fix_angle(double a);
static double deg2rad(double d);
static double rad2deg(double r);
static String int_to_string(int num);

// FUNGSI UTAMA - DENGAN PARAMETER ELEVASI
void get_prayer_times(int year, int month, int day, 
                      double _latitude, double _longitude, 
                      double _timezone, 
                      double _elevation,  // PARAMETER ELEVASI
                      double times[]);

void get_float_time_parts(double time, int& hours, int& minutes);

#endif

Kode Program

Contoh kode dasar adalah sebagai berikut:

#include <Wire.h>
#include "RTClib.h"
#include "PrayerTimes.h"

RTC_DS3231 rtc;

// ========================
// KONFIGURASI LOKASI
// ========================
double LATITUDE = 4.107927;
double LONGITUDE = 96.196475;
int TIMEZONE = 7;
double ELEVATION = 10.0;
String LOCATION_NAME = "Nagan Raya";

// ========================
// METODE PERHITUNGAN
// ========================
String CALCULATION_METHOD = "MUI";

// ========================
// OFFSET OTOMATIS PER BULAN
// ========================
// Format: {Fajr, Sunrise, Dhuhr, Asr, Maghrib, Isha}
const int MONTHLY_OFFSETS[12][6] = {
  // Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec
  {3, -3, 3, 3, 3, 3},  // Januari
  {3, -3, 4, 3, 3, 3},  // Februari
  {2, -2, 4, 3, 3, 2},  // Maret
  {3, -3, 4, 3, 3, 2},  // April
  {2, -3, 3, 3, 3, 3},  // Mei
  {3, -3, 3, 2, 3, 3},  // Juni
  {2, -3, 4, 3, 4, 3},  // Juli
  {2, -3, 4, 3, 4, 3},  // Agustus
  {3, -3, 4, 3, 3, 2},  // September
  {2, -3, 4, 3, 3, 3},  // Oktober
  {3, -3, 4, 3, 3, 3},  // November
  {3, -2, 4, 2, 3, 2}   // Desember
};

// Variabel offset aktif (akan diisi otomatis)
int FAJR_OFFSET = 0;
int SUNRISE_OFFSET = 0;
int DHUHR_OFFSET = 0;
int ASR_OFFSET = 0;
int MAGHRIB_OFFSET = 0;
int ISHA_OFFSET = 0;

// ========================
// MODE DAN VARIABEL
// ========================
bool CONFIG_MODE = false;
bool AUTO_OFFSET_ENABLED = true;  // Toggle otomatis/manual

#define NUM_PRAYER_TIMES 11  // Tambah Imsak dan Dhuha
double times[NUM_PRAYER_TIMES];

int inputYear = 2026;
int inputMonth = 2;
int inputDay = 3;

// ========================
// UPDATE OFFSET OTOMATIS BERDASARKAN BULAN
// ========================
void updateMonthlyOffsets(int month) {
  if (!AUTO_OFFSET_ENABLED) return;
  
  if (month < 1 || month > 12) return;
  
  int idx = month - 1;  // Array 0-based
  FAJR_OFFSET = MONTHLY_OFFSETS[idx][0];
  SUNRISE_OFFSET = MONTHLY_OFFSETS[idx][1];
  DHUHR_OFFSET = MONTHLY_OFFSETS[idx][2];
  ASR_OFFSET = MONTHLY_OFFSETS[idx][3];
  MAGHRIB_OFFSET = MONTHLY_OFFSETS[idx][4];
  ISHA_OFFSET = MONTHLY_OFFSETS[idx][5];
}

// ========================
// Fungsi pilih metode perhitungan
// ========================
void setPrayerMethod(String method) {
  set_calc_method(Custom);
  set_asr_method(Shafii);
  set_high_lats_adjust_method(AngleBased);
  
  if (method == "MUI" || method == "Singapore") {
    set_fajr_angle(20);
    set_isha_angle(18);
  } else if (method == "MWL" || method == "Turkey") {
    set_fajr_angle(18);
    set_isha_angle(17);
  } else if (method == "ISNA") {
    set_fajr_angle(15);
    set_isha_angle(15);
  } else if (method == "Egypt") {
    set_fajr_angle(19.5);
    set_isha_angle(17.5);
  } else if (method == "Makkah") {
    set_fajr_angle(18.5);
    set_isha_angle(18);
  } else if (method == "Karachi") {
    set_fajr_angle(18);
    set_isha_angle(18);
  } else {
    set_fajr_angle(18);
    set_isha_angle(17);
  }
}

// ========================
// Helper print
// ========================
void p(char *fmt, ...) {
  char tmp[128];
  va_list args;
  va_start(args, fmt);
  vsnprintf(tmp, 128, fmt, args);
  va_end(args);
  Serial.print(tmp);
}

// ========================
// Parse tanggal DD-MM-YYYY
// ========================
bool parseDate(String dateStr, int &year, int &month, int &day) {
  int firstDash = dateStr.indexOf('-');
  int secondDash = dateStr.indexOf('-', firstDash + 1);
  
  if (firstDash == -1 || secondDash == -1) {
    return false;
  }
  
  day = dateStr.substring(0, firstDash).toInt();
  month = dateStr.substring(firstDash + 1, secondDash).toInt();
  year = dateStr.substring(secondDash + 1).toInt();
  
  if (day < 1 || day > 31 || month < 1 || month > 12 || year < 2000 || year > 2100) {
    return false;
  }
  
  return true;
}

// ========================
// Tampilkan jadwal sholat
// ========================
void displayPrayerTimes() {
  // Update offset otomatis berdasarkan bulan
  updateMonthlyOffsets(inputMonth);
  
  setPrayerMethod(CALCULATION_METHOD);
  get_prayer_times(inputYear, inputMonth, inputDay,
                   LATITUDE, LONGITUDE, TIMEZONE,
                   ELEVATION,
                   times);
  
  // PENTING: Hitung waktu terkoreksi DULU
  double fajr_corrected = times[0] + (FAJR_OFFSET / 60.0);
  double sunrise_corrected = times[1] + (SUNRISE_OFFSET / 60.0);
  
  // Baru hitung Imsak dan Dhuha dari waktu yang sudah terkoreksi
  double imsak = fajr_corrected - (10.0 / 60.0);      // (Subuh + koreksi) - 10 menit
  double dhuha = sunrise_corrected + (20.0 / 60.0);   // (Terbit + koreksi) + 20 menit
  
  // Reorganisasi array times dengan urutan baru
  // [0]=Imsak, [1]=Fajr, [2]=Terbit, [3]=Dhuha, [4]=Dhuhr, [5]=Asr, [6]=Terbenam, [7]=Maghrib, [8]=Isha
  double orderedTimes[NUM_PRAYER_TIMES];
  orderedTimes[0] = imsak;           // Imsak (sudah final, tidak perlu offset lagi)
  orderedTimes[1] = times[0];        // Fajr (akan ditambah offset)
  orderedTimes[2] = times[1];        // Terbit (akan ditambah offset)
  orderedTimes[3] = dhuha;           // Dhuha (sudah final, tidak perlu offset lagi)
  orderedTimes[4] = times[2];        // Dhuhr
  orderedTimes[5] = times[3];        // Asr
  orderedTimes[6] = times[4];        // Terbenam
  orderedTimes[7] = times[5];        // Maghrib
  orderedTimes[8] = times[6];        // Isha
  orderedTimes[9] = 0;               // Reserved
  orderedTimes[10] = 0;              // Reserved
  
  // Offset untuk setiap waktu
  int offsets[NUM_PRAYER_TIMES] = {
    0,               // 0: Imsak (TIDAK ada offset, sudah final)
    FAJR_OFFSET,     // 1: Fajr
    SUNRISE_OFFSET,  // 2: Terbit
    0,               // 3: Dhuha (TIDAK ada offset, sudah final)
    DHUHR_OFFSET,    // 4: Dhuhr
    ASR_OFFSET,      // 5: Asr
    0,               // 6: Terbenam
    MAGHRIB_OFFSET,  // 7: Maghrib
    ISHA_OFFSET,     // 8: Isha
    0,               // 9: Reserved
    0                // 10: Reserved
  };
  
  Serial.println();
  Serial.println("=================================");
  Serial.println("  JADWAL WAKTU SHOLAT v3.1");
  Serial.println("=================================");
  Serial.print("Lokasi   : ");
  Serial.println(LOCATION_NAME);
  Serial.print("Koordinat: ");
  Serial.print(LATITUDE, 6);
  Serial.print(", ");
  Serial.println(LONGITUDE, 6);
  Serial.print("Elevasi  : ");
  Serial.print(ELEVATION, 1);
  Serial.println(" m");
  Serial.print("Metode   : ");
  Serial.println(CALCULATION_METHOD);
  Serial.print("Tanggal  : ");
  Serial.print(inputDay);
  Serial.print("/");
  Serial.print(inputMonth);
  Serial.print("/");
  Serial.println(inputYear);
  Serial.print("Timezone : UTC");
  if (TIMEZONE >= 0) Serial.print("+");
  Serial.println(TIMEZONE);
  
  // Tampilkan status auto-offset
  Serial.print("Auto-Adj : ");
  Serial.println(AUTO_OFFSET_ENABLED ? "AKTIF" : "MANUAL");
  
  Serial.println("=================================");
  
  // Nama waktu dalam Bahasa Indonesia
  const char *displayNames[] = {
    "Imsak", "Subuh", "Terbit", "Dhuha", "Dzuhur",
    "Ashar", "Terbenam", "Maghrib", "Isya"
  };
  
  for (int i = 0; i < 9; i++) {  // Hanya tampilkan 9 waktu utama
    int hours, minutes;
    double correctedTime = orderedTimes[i] + (offsets[i] / 60.0);
    get_float_time_parts(correctedTime, hours, minutes);
    
    // Handle overflow
    while (minutes >= 60) {
      hours++;
      minutes -= 60;
    }
    while (minutes < 0) {
      hours--;
      minutes += 60;
    }
    while (hours >= 24) hours -= 24;
    while (hours < 0) hours += 24;
    
    p("%-10s : %02d:%02d", displayNames[i], hours, minutes);
    
    if (offsets[i] != 0) {
      p(" (%+d)", offsets[i]);
    }
    
    // Tandai waktu yang terpengaruh koreksi elevasi
    if (ELEVATION > 0 && (i == 2 || i == 6 || i == 7)) {
      Serial.print(" *");
    }
    
    Serial.println();
  }
  
  Serial.println("=================================");
  
  if (ELEVATION > 0) {
    Serial.println("* = Sudah terkoreksi elevasi");
    double elevCorr = 1.76 * sqrt(ELEVATION) / 60.0;
    Serial.print("  Koreksi: ±");
    Serial.print(elevCorr, 1);
    Serial.println(" menit");
    Serial.println("=================================");
  }
  
  Serial.println("Catatan:");
  Serial.println("• Imsak = 10 menit sebelum Subuh");
  Serial.println("• Dhuha = 20 menit setelah Terbit");
  Serial.println("=================================");
}

// ========================
// MODE KONFIGURASI
// ========================
void showConfigMenu() {
  Serial.println();
  Serial.println("=================================");
  Serial.println("  MODE KONFIGURASI");
  Serial.println("=================================");
  Serial.println("Perintah yang tersedia:");
  Serial.println();
  Serial.println("1. SET LOKASI");
  Serial.println("   Format: LOC <nama>,<lat>,<lon>,<tz>,<elev>");
  Serial.println("   Contoh: LOC Jakarta,-6.2088,106.8456,7,8");
  Serial.println();
  Serial.println("2. SET METODE");
  Serial.println("   Format: METHOD <metode>");
  Serial.println("   Pilihan: MUI, MWL, ISNA, Egypt,");
  Serial.println("            Makkah, Karachi, Singapore");
  Serial.println("   Contoh: METHOD MUI");
  Serial.println();
  Serial.println("3. SET KOREKSI MANUAL");
  Serial.println("   Format: OFFSET <waktu> <menit>");
  Serial.println("   Waktu: FAJR, SUNRISE, DHUHR,");
  Serial.println("          ASR, MAGHRIB, ISHA");
  Serial.println("   Contoh: OFFSET FAJR +3");
  Serial.println("   Otomatis nonaktif jika pakai ini");
  Serial.println();
  Serial.println("4. TOGGLE AUTO-OFFSET");
  Serial.println("   Perintah: AUTO [ON/OFF]");
  Serial.println("   Contoh: AUTO ON");
  Serial.println();
  Serial.println("5. TAMPILKAN KONFIGURASI");
  Serial.println("   Perintah: SHOW");
  Serial.println();
  Serial.println("6. KELUAR DARI CONFIG");
  Serial.println("   Perintah: EXIT");
  Serial.println("=================================");
  Serial.println();
}

void showCurrentConfig() {
  // Update offset dulu sebelum menampilkan
  if (AUTO_OFFSET_ENABLED) {
    updateMonthlyOffsets(inputMonth);
  }
  
  Serial.println();
  Serial.println("=== KONFIGURASI SAAT INI ===");
  Serial.print("Lokasi     : ");
  Serial.println(LOCATION_NAME);
  Serial.print("Latitude   : ");
  Serial.println(LATITUDE, 6);
  Serial.print("Longitude  : ");
  Serial.println(LONGITUDE, 6);
  Serial.print("Elevasi    : ");
  Serial.print(ELEVATION, 1);
  Serial.println(" meter");
  Serial.print("Timezone   : UTC");
  if (TIMEZONE >= 0) Serial.print("+");
  Serial.println(TIMEZONE);
  Serial.print("Metode     : ");
  Serial.println(CALCULATION_METHOD);
  Serial.print("Auto-Adjust: ");
  Serial.println(AUTO_OFFSET_ENABLED ? "AKTIF" : "MANUAL");
  
  Serial.println("Koreksi (menit):");
  Serial.print("  Fajr     : ");
  Serial.println(FAJR_OFFSET);
  Serial.print("  Sunrise  : ");
  Serial.println(SUNRISE_OFFSET);
  Serial.print("  Dhuhr    : ");
  Serial.println(DHUHR_OFFSET);
  Serial.print("  Asr      : ");
  Serial.println(ASR_OFFSET);
  Serial.print("  Maghrib  : ");
  Serial.println(MAGHRIB_OFFSET);
  Serial.print("  Isha     : ");
  Serial.println(ISHA_OFFSET);
  
  if (AUTO_OFFSET_ENABLED) {
    Serial.print("  (Bulan   : ");
    const char* months[] = {"", "Jan", "Feb", "Mar", "Apr", "Mei", "Jun", 
                            "Jul", "Agu", "Sep", "Okt", "Nov", "Des"};
    Serial.print(months[inputMonth]);
    Serial.println(")");
  }
  
  Serial.println("============================");
  Serial.println();
}

void processConfigCommand(String cmd) {
  cmd.trim();
  cmd.toUpperCase();
  
  if (cmd == "HELP" || cmd == "?") {
    showConfigMenu();
  } 
  else if (cmd == "SHOW") {
    showCurrentConfig();
  } 
  else if (cmd == "EXIT") {
    CONFIG_MODE = false;
    Serial.println("Keluar dari mode konfigurasi.");
    Serial.println("Masukkan tanggal (DD-MM-YYYY):");
  } 
  else if (cmd.startsWith("AUTO ")) {
    String param = cmd.substring(5);
    param.trim();
    
    if (param == "ON") {
      AUTO_OFFSET_ENABLED = true;
      Serial.println("✓ Auto-offset AKTIF");
      updateMonthlyOffsets(inputMonth);  // Update langsung
      showCurrentConfig();
    } 
    else if (param == "OFF") {
      AUTO_OFFSET_ENABLED = false;
      Serial.println("✓ Auto-offset NONAKTIF (mode manual)");
    } 
    else {
      Serial.println("✗ Gunakan: AUTO ON atau AUTO OFF");
    }
  }
  else if (cmd.startsWith("LOC ")) {
    String params = cmd.substring(4);
    int comma1 = params.indexOf(',');
    int comma2 = params.indexOf(',', comma1 + 1);
    int comma3 = params.indexOf(',', comma2 + 1);
    int comma4 = params.indexOf(',', comma3 + 1);
    
    if (comma1 > 0 && comma2 > 0 && comma3 > 0) {
      LOCATION_NAME = params.substring(0, comma1);
      LATITUDE = params.substring(comma1 + 1, comma2).toDouble();
      LONGITUDE = params.substring(comma2 + 1, comma3).toDouble();
      TIMEZONE = params.substring(comma3 + 1, comma4 > 0 ? comma4 : params.length()).toInt();
      
      if (comma4 > 0) {
        ELEVATION = params.substring(comma4 + 1).toDouble();
      }
      
      Serial.println("✓ Lokasi berhasil diubah!");
      showCurrentConfig();
    } else {
      Serial.println("✗ Format salah! Gunakan: LOC Nama,lat,lon,tz,elev");
    }
  } 
  else if (cmd.startsWith("METHOD ")) {
    String method = cmd.substring(7);
    method.trim();
    
    if (method == "MUI" || method == "MWL" || method == "ISNA" || 
        method == "EGYPT" || method == "MAKKAH" || method == "KARACHI" || 
        method == "SINGAPORE" || method == "TURKEY") {
      CALCULATION_METHOD = method;
      Serial.println("✓ Metode berhasil diubah ke: " + method);
    } else {
      Serial.println("✗ Metode tidak valid!");
      Serial.println("Pilihan: MUI, MWL, ISNA, Egypt, Makkah, Karachi, Singapore, Turkey");
    }
  } 
  else if (cmd.startsWith("OFFSET ")) {
    String params = cmd.substring(7);
    int spacePos = params.indexOf(' ');
    
    if (spacePos > 0) {
      String prayerTime = params.substring(0, spacePos);
      int offset = params.substring(spacePos + 1).toInt();
      
      // Nonaktifkan auto-offset jika setting manual
      AUTO_OFFSET_ENABLED = false;
      
      if (prayerTime == "FAJR") FAJR_OFFSET = offset;
      else if (prayerTime == "SUNRISE") SUNRISE_OFFSET = offset;
      else if (prayerTime == "DHUHR") DHUHR_OFFSET = offset;
      else if (prayerTime == "ASR") ASR_OFFSET = offset;
      else if (prayerTime == "MAGHRIB") MAGHRIB_OFFSET = offset;
      else if (prayerTime == "ISHA") ISHA_OFFSET = offset;
      else {
        Serial.println("✗ Waktu tidak valid!");
        AUTO_OFFSET_ENABLED = true;  // Revert
        return;
      }
      
      Serial.println("✓ Koreksi " + prayerTime + " diubah ke: " + String(offset));
      Serial.println("Auto-offset NONAKTIF (mode manual)");
    } else {
      Serial.println("✗ Format salah! Gunakan: OFFSET FAJR +3");
    }
  } 
  else {
    Serial.println("✗ Perintah tidak dikenal. Ketik HELP untuk bantuan.");
  }
}

// ========================
// SETUP
// ========================
void setup() {
  Serial.begin(9600);
  delay(1000);
  
  if (!rtc.begin()) {
    Serial.println("Warning: RTC tidak terdeteksi");
  } else {
    if (rtc.lostPower()) {
      rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    }
    DateTime now = rtc.now();
    inputYear = now.year();
    inputMonth = now.month();
    inputDay = now.day();
  }
  
  // Update offset berdasarkan bulan saat ini
  updateMonthlyOffsets(inputMonth);
  
  Serial.println();
  Serial.println("=================================");
  Serial.println(" KALKULATOR WAKTU SHOLAT");
  Serial.println(" Universal Prayer Times");
  Serial.println("=================================");
  Serial.println("Versi : 3.1 (Imsak & Dhuha)");
  Serial.println("Author: CHIPPIKO");
  Serial.println("=================================");
  Serial.println();
  
  showCurrentConfig();
  
  Serial.println("CARA PENGGUNAAN:");
  Serial.println("1. Masukkan tanggal: DD-MM-YYYY");
  Serial.println("   Contoh: 03-02-2026");
  Serial.println();
  Serial.println("2. Ketik CONFIG untuk konfigurasi");
  Serial.println("   (ubah lokasi, metode, elevasi)");
  Serial.println("=================================");
  Serial.println();
  
  if (rtc.begin() && !rtc.lostPower()) {
    Serial.println("Jadwal hari ini:");
    displayPrayerTimes();
    Serial.println();
  }
  
  Serial.println("Masukkan tanggal atau ketik CONFIG:");
}

// ========================
// LOOP
// ========================
void loop() {
  if (Serial.available() > 0) {
    String input = Serial.readStringUntil('\n');
    input.trim();
    
    if (input.length() == 0) return;
    
    Serial.print("> ");
    Serial.println(input);
    
    if (input.equalsIgnoreCase("CONFIG")) {
      CONFIG_MODE = true;
      showConfigMenu();
      return;
    }
    
    if (CONFIG_MODE) {
      processConfigCommand(input);
      return;
    }
    
    if (parseDate(input, inputYear, inputMonth, inputDay)) {
      displayPrayerTimes();
      Serial.println();
      Serial.println("Masukkan tanggal atau CONFIG:");
    } else {
      Serial.println("✗ Format salah!");
      Serial.println("Format: DD-MM-YYYY atau CONFIG");
      Serial.println();
    }
  }
  
  delay(100);
}


Hasil

Setelah kode di upload ke Arduino, buka serial monitor dan kita akan mendapatkan hasil sebagai gambar berikut:

Versi Sebelumnya

v1.0 – Initial Release

  • Perhitungan dasar waktu sholat (Fajr, Sunrise, Dhuhr, Asr, Sunset, Maghrib, Isha)
  • Menggunakan library PrayerTimes original
  • Support multiple calculation methods (MWL, ISNA, Egypt, Makkah, Karachi)
  • Input manual tanggal DD-MM-YYYY
  • RTC DS3231 integration
  • Issue: Selisih waktu dengan referensi cukup besar (5-10 menit)

v1.5 – Manual Offset System

  • Tambah koreksi manual per waktu sholat (FAJR_OFFSET, DHUHR_OFFSET, dll)
  • Tampilkan indikator koreksi dalam format (+3) atau (-2)
  • Issue: Offset harus diubah manual di kode setiap bulan

v2.0 – Julian Date Fix

  • Perbaikan algoritma get_julian_date() menggunakan metode Jean Meeus
  • Support elevasi/ketinggian lokasi (elevation correction)
  • Formula dip angle: 1.76 * sqrt(elevation) untuk Sunrise/Sunset/Maghrib
  • Indikator * untuk waktu yang terpengaruh koreksi elevasi
  • Issue: Selisih waktu meningkat seiring tahun (5 menit/tahun untuk 2027-2028)

v2.1 – Config Mode

  • Mode konfigurasi interaktif via Serial
  • Command: LOC, METHOD, OFFSET, SHOW, EXIT
  • Ubah lokasi tanpa edit kode
  • Ubah metode perhitungan on-the-fly
  • Set koreksi manual per waktu sholat

v2.2 – High Precision Sun Position

  • Algoritma sun_position() dengan presisi tinggi (Meeus)
  • Menghitung dengan Julian Centuries (T) bukan day count
  • Koreksi eccentricity orbit bumi
  • Koreksi equation of center
  • Koreksi nutasi (goyangan sumbu bumi)
  • Obliquity yang dinamis
  • Result: Selisih tahunan berkurang drastis

v3.0 – Auto Monthly Offset

  • Auto-adjustment offset berdasarkan bulan
  • Array MONTHLY_OFFSETS[12][6] dengan data tuning per bulan
  • Toggle mode AUTO/MANUAL
  • Command baru: AUTO ON / AUTO OFF
  • Offset otomatis update saat ganti bulan
  • Indikator status Auto-Adj : AKTIF
  • Tampilkan bulan aktif: (Bulan : Feb)
  • Result: Tidak perlu edit kode setiap bulan

v3.1 – Imsak & Dhuha (Current)

  • Tambah waktu Imsak (10 menit sebelum Subuh)
  • Tambah waktu Dhuha (20 menit setelah Terbit)
  • Penamaan Bahasa Indonesia:
    • Fajr → Subuh
    • Sunrise → Terbit
    • Sunset → Terbenam
    • Dhuhr → Dzuhur
    • Asr → Ashar
    • Isha → Isya
  • Urutan kronologis: Imsak → Subuh → Terbit → Dhuha → Dzuhur → Ashar → Terbenam → Maghrib → Isya
  • Fix perhitungan: Imsak = (Subuh + koreksi) – 10 menit
  • Fix perhitungan: Dhuha = (Terbit + koreksi) + 20 menit
  • Catatan otomatis di output
  • Update offset saat setup() dan showCurrentConfig()


Semoga artikel Kode Progam Jadwal Sholat Arduino C++ ini bermanfaat. Jika teman-teman ada saran dan kritikan, mohon contact kami.

Update: 4 Feb 2026