Upload files to "/"
This commit is contained in:
commit
fa79a7f73e
|
|
@ -0,0 +1,613 @@
|
|||
/***************** KONFIGURASI BLYNK *****************/
|
||||
#define BLYNK_TEMPLATE_ID "TMPL6IJqYTM5v"
|
||||
#define BLYNK_TEMPLATE_NAME "IOT Hamster"
|
||||
#define BLYNK_AUTH_TOKEN "cyVp3mgGDuB3pu0sGBoJlIGYKao-HKS1"
|
||||
|
||||
/***************** LIBRARY *****************/
|
||||
#include <WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#include <BlynkSimpleEsp32.h>
|
||||
#include "esp_system.h"
|
||||
#include "DHT.h"
|
||||
#include <Wire.h>
|
||||
#include <RTClib.h>
|
||||
#include <ESP32Servo.h>
|
||||
|
||||
/***************** PIN SENSOR *****************/
|
||||
#define DHTPIN 4
|
||||
#define DHTTYPE DHT22
|
||||
#define DHT_POWER 16
|
||||
#define RELAY_KIPAS 27
|
||||
#define RELAY_PUMP 26
|
||||
#define SERVO_PIN 14
|
||||
#define WATER_SENSOR_PIN 35
|
||||
|
||||
DHT dht(DHTPIN, DHTTYPE);
|
||||
RTC_DS3231 rtc;
|
||||
Servo servo;
|
||||
|
||||
/***************** VARIABEL *****************/
|
||||
int lastDay = -1;
|
||||
|
||||
bool manualKipas = false;
|
||||
bool manualPump = false;
|
||||
float lastSuhu = NAN;
|
||||
|
||||
bool feedingDoneToday = false;
|
||||
bool servoAutoAktif = false;
|
||||
unsigned long servoAutoMulai = 0;
|
||||
|
||||
const int JAM_FEEDING = 18;
|
||||
const int MENIT_FEEDING = 0;
|
||||
const unsigned long DURASI_BUKA_SERVO = 300;
|
||||
|
||||
unsigned long lastFeedingTime = 0;
|
||||
const unsigned long INTERVAL_FEEDING = 60000; // 1 menit (60000 ms)
|
||||
|
||||
/* ===== PERBAIKAN DHT ===== */
|
||||
bool pumpAktif = false;
|
||||
unsigned long waktuPompaBerubah = 0;
|
||||
bool dhtValid = false;
|
||||
bool dhtEnabled = true;
|
||||
unsigned long lastPrint = 0;
|
||||
/* ======================== */
|
||||
|
||||
/* ===== AUTO RESET DHT NON-BLOCKING (MAX 3 DETIK) ===== */
|
||||
unsigned long dhtErrorSejak = 0;
|
||||
unsigned long dhtResetMulai = 0;
|
||||
bool dhtSedangReset = false;
|
||||
|
||||
const unsigned long DHT_TIMEOUT = 3000; //
|
||||
const unsigned long DHT_RESET_OFF = 500; //
|
||||
/* ==================================================== */
|
||||
|
||||
/* ===== TAMBAHAN AUTO KIPAS ===== */
|
||||
unsigned long waktuKipasDimatikanManual = 0;
|
||||
bool tungguAutoKipas = false;
|
||||
/* ============================== */
|
||||
|
||||
/* ===== TAMBAHAN AUTO OFF POMPA ===== */
|
||||
const unsigned long POMPA_AUTO_OFF = 10000;
|
||||
/* ================================= */
|
||||
|
||||
/* ===== VARIABEL WATER SENSOR ===== */
|
||||
int waterValue = 0;
|
||||
int waterPercent = 0; // <-- TAMBAHAN
|
||||
float waterFiltered = 0;
|
||||
bool adaAir = false;
|
||||
#define WATER_EMPTY 100 //air habis
|
||||
#define WATER_FULL 950 //air penuh
|
||||
|
||||
bool pompaOtomatisAktif = false;
|
||||
|
||||
/* NAMA DAN PASSWORD WIFI */
|
||||
|
||||
char ssid[] = "OPPO Reno5 F";
|
||||
char pass[] = "87654321";
|
||||
|
||||
WiFiClientSecure client;
|
||||
BlynkTimer timer;
|
||||
|
||||
/***************** STATUS SERVO KE BLYNK (V5) *****************/
|
||||
void updateServoStatus(int posisi) {
|
||||
if (posisi == 0) {
|
||||
Blynk.virtualWrite(V5, "Tertutup");
|
||||
} else if (posisi == 90) {
|
||||
Blynk.virtualWrite(V5, "Terbuka");
|
||||
} else {
|
||||
Blynk.virtualWrite(V5, "Bergerak");
|
||||
}
|
||||
}
|
||||
|
||||
/************** CONTROL MANUAL DARI BLYNK (Kipas) **************/
|
||||
BLYNK_WRITE(V1) {
|
||||
int state = param.asInt();
|
||||
manualKipas = true;
|
||||
digitalWrite(RELAY_KIPAS, state ? LOW : HIGH);
|
||||
|
||||
if (state == 0) {
|
||||
waktuKipasDimatikanManual = millis();
|
||||
tungguAutoKipas = true;
|
||||
} else {
|
||||
tungguAutoKipas = false;
|
||||
}
|
||||
}
|
||||
|
||||
/************** CONTROL MANUAL DARI BLYNK (Pompa) **************/
|
||||
BLYNK_WRITE(V2) {
|
||||
int state = param.asInt();
|
||||
|
||||
if (waterValue >= WATER_FULL && state == 1) {
|
||||
Blynk.virtualWrite(V2, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
manualPump = true;
|
||||
pompaOtomatisAktif = false;
|
||||
dhtEnabled = false;
|
||||
|
||||
digitalWrite(RELAY_PUMP, state ? LOW : HIGH);
|
||||
pumpAktif = state;
|
||||
dhtEnabled = false;
|
||||
waktuPompaBerubah = millis();
|
||||
|
||||
if (state == 1) {
|
||||
dhtEnabled = false;
|
||||
|
||||
String pesan = "💧 POMPA MANUAL MENYALA\n";
|
||||
pesan += "Status: Minum Dibuka\n";
|
||||
pesan += "Level Air: " + String(waterValue);
|
||||
|
||||
kirimTelegram(pesan);
|
||||
|
||||
} else {
|
||||
dhtEnabled = true;
|
||||
|
||||
String pesan = "💧 POMPA MANUAL DIMATIKAN\n";
|
||||
pesan += "Status: Minum Ditutup\n";
|
||||
pesan += "Level Air: " + String(waterValue);
|
||||
|
||||
kirimTelegram(pesan);
|
||||
}
|
||||
}
|
||||
|
||||
/************** CONTROL MANUAL DARI BLYNK (SERVO) **************/
|
||||
BLYNK_WRITE(V8) {
|
||||
|
||||
if (param.asInt() == 1) {
|
||||
|
||||
servo.write(90);
|
||||
updateServoStatus(90);
|
||||
|
||||
kirimLogFeeding("Manual Feeding Dimulai");
|
||||
|
||||
delay(1000);
|
||||
|
||||
servo.write(0);
|
||||
updateServoStatus(0);
|
||||
|
||||
kirimLogFeeding("Manual Feeding Selesai");
|
||||
|
||||
Blynk.virtualWrite(V8, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/***************** KIRIM DATA KE BLYNK *****************/
|
||||
void sendToBlynk() {
|
||||
|
||||
DateTime now = rtc.now();
|
||||
int jam = now.hour();
|
||||
int menit = now.minute();
|
||||
|
||||
Blynk.virtualWrite(V3, jam);
|
||||
Blynk.virtualWrite(V4, menit);
|
||||
|
||||
char waktu[10];
|
||||
sprintf(waktu, "%02d:%02d", jam, menit);
|
||||
Blynk.virtualWrite(V7, waktu);
|
||||
|
||||
if (dhtValid) {
|
||||
Blynk.virtualWrite(V10, 1);
|
||||
Blynk.virtualWrite(V11, "DHT BERFUNGSI");
|
||||
} else {
|
||||
Blynk.virtualWrite(V10, 0);
|
||||
Blynk.virtualWrite(V11, "DHT ERROR");
|
||||
}
|
||||
}
|
||||
|
||||
/***************** SETUP *****************/
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
pinMode(DHT_POWER, OUTPUT);
|
||||
digitalWrite(DHT_POWER, HIGH);
|
||||
delay(2000);
|
||||
|
||||
pinMode(RELAY_KIPAS, OUTPUT);
|
||||
pinMode(RELAY_PUMP, OUTPUT);
|
||||
digitalWrite(RELAY_KIPAS, HIGH);
|
||||
digitalWrite(RELAY_PUMP, HIGH);
|
||||
|
||||
pinMode(WATER_SENSOR_PIN, INPUT); // === TAMBAHAN ===
|
||||
|
||||
dht.begin();
|
||||
rtc.begin();
|
||||
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
|
||||
|
||||
servo.attach(SERVO_PIN, 500, 2400);
|
||||
servo.write(0);
|
||||
updateServoStatus(0);
|
||||
|
||||
Serial.println("\n📶 WiFi Terhubung");
|
||||
Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass);
|
||||
|
||||
timer.setInterval(2000L, sendToBlynk);
|
||||
timer.setInterval(3000L, bacaDHT);
|
||||
timer.setInterval(500L, bacaWaterSensor); // === TAMBAHAN ===
|
||||
// timer.setInterval(300000L, printSuhuSerial);
|
||||
|
||||
Serial.println("Sistem IoT + Blynk berjalan...");
|
||||
//Pompa set adc
|
||||
analogSetPinAttenuation(WATER_SENSOR_PIN, ADC_11db);
|
||||
analogReadResolution(12);
|
||||
timer.setInterval(30000L, kirimSuhuTelegram); // tiap 10 detik
|
||||
|
||||
/***************** WAKTU *****************/
|
||||
configTime(7 * 3600, 0, "pool.ntp.org"); // WIB = GMT+7
|
||||
|
||||
struct tm timeinfo;
|
||||
if (getLocalTime(&timeinfo)) {
|
||||
rtc.adjust(DateTime(
|
||||
timeinfo.tm_year + 1900,
|
||||
timeinfo.tm_mon + 1,
|
||||
timeinfo.tm_mday,
|
||||
timeinfo.tm_hour,
|
||||
timeinfo.tm_min,
|
||||
timeinfo.tm_sec
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/************** TELEGRAM CONFIG **************/
|
||||
const char* botToken = "8710332774:AAHdWZwsIB2KbkkuvWBnFwWQgeQ15Io_L_c";
|
||||
const char* chatID = "5535845901";
|
||||
|
||||
void kirimTelegram(String pesan) {
|
||||
|
||||
client.setInsecure(); // bypass SSL
|
||||
|
||||
if (!client.connect("api.telegram.org", 443)) {
|
||||
Serial.println("Gagal koneksi ke Telegram");
|
||||
return;
|
||||
}
|
||||
|
||||
String url = "/bot" + String(botToken) + "/sendMessage?chat_id=" + String(chatID) + "&text=" + pesan;
|
||||
|
||||
client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: api.telegram.org\r\n" + "Connection: close\r\n\r\n");
|
||||
|
||||
delay(500);
|
||||
while (client.available()) {
|
||||
client.read();
|
||||
}
|
||||
|
||||
client.stop();
|
||||
}
|
||||
|
||||
/***************** LOOP *****************/
|
||||
void loop() {
|
||||
Blynk.run();
|
||||
timer.run();
|
||||
|
||||
autoFeedingRTC();
|
||||
|
||||
|
||||
if (dhtValid) {
|
||||
if (!manualKipas) {
|
||||
if (lastSuhu > 30) {
|
||||
digitalWrite(RELAY_KIPAS, LOW);
|
||||
Blynk.virtualWrite(V1, 1);
|
||||
} else {
|
||||
digitalWrite(RELAY_KIPAS, HIGH);
|
||||
Blynk.virtualWrite(V1, 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
digitalWrite(RELAY_KIPAS, HIGH);
|
||||
}
|
||||
|
||||
if (tungguAutoKipas && manualKipas && dhtValid) {
|
||||
if (lastSuhu > 30 && millis() - waktuKipasDimatikanManual >= 5000) {
|
||||
digitalWrite(RELAY_KIPAS, LOW);
|
||||
Blynk.virtualWrite(V1, 1);
|
||||
tungguAutoKipas = false;
|
||||
Serial.println("🔥 Kipas otomatis menyala kembali");
|
||||
}
|
||||
}
|
||||
|
||||
if (manualKipas && millis() - waktuKipasDimatikanManual > 10000) {
|
||||
manualKipas = false;
|
||||
}
|
||||
|
||||
if (millis() - lastPrint >= 300000) {
|
||||
|
||||
Serial.print("🌡 Suhu: ");
|
||||
Serial.print(lastSuhu);
|
||||
Serial.println(" °C");
|
||||
|
||||
lastPrint = millis();
|
||||
}
|
||||
|
||||
// ===== AUTO RESET DHT NON-BLOCKING =====
|
||||
if (!dhtValid) {
|
||||
|
||||
// mulai hitung error
|
||||
if (dhtErrorSejak == 0 && !dhtSedangReset) {
|
||||
dhtErrorSejak = millis();
|
||||
}
|
||||
|
||||
// jika error >= 3 detik → mulai reset
|
||||
if (!dhtSedangReset && millis() - dhtErrorSejak >= DHT_TIMEOUT) {
|
||||
// Serial.println("⚠️ DHT error > 3 detik → reset dimulai");
|
||||
digitalWrite(DHT_POWER, LOW); // matikan DHT
|
||||
dhtResetMulai = millis();
|
||||
dhtSedangReset = true;
|
||||
}
|
||||
|
||||
if (dhtSedangReset && millis() - dhtResetMulai >= DHT_RESET_OFF) {
|
||||
digitalWrite(DHT_POWER, HIGH);
|
||||
|
||||
// ===== RELEASE SYSTEM LOCK =====
|
||||
dhtSedangReset = false;
|
||||
dhtErrorSejak = 0;
|
||||
dhtEnabled = true; // 🔓 izinkan baca DHT lagi
|
||||
pumpAktif = false; // 🔓 reset status pompa
|
||||
Blynk.virtualWrite(V2, 0);
|
||||
|
||||
Serial.println("🔄 DHT reset selesai, sistem dilepas");
|
||||
}
|
||||
|
||||
} else {
|
||||
// jika DHT normal → reset semua flag
|
||||
dhtErrorSejak = 0;
|
||||
dhtSedangReset = false;
|
||||
}
|
||||
// ======================================
|
||||
|
||||
// ===== AUTO WATER LEVEL =====
|
||||
|
||||
// Jika air habis → aktifkan mode otomatis
|
||||
if (!adaAir && !manualPump) {
|
||||
if (!pumpAktif) {
|
||||
pompaOtomatisAktif = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Jika tangki penuh → pompa harus mati
|
||||
if (waterPercent >= 80) {
|
||||
digitalWrite(RELAY_PUMP, HIGH);
|
||||
pumpAktif = false;
|
||||
pompaOtomatisAktif = false;
|
||||
manualPump = false;
|
||||
Blynk.virtualWrite(V2, 0);
|
||||
String pesan = "✅ POMPA OTOMATIS BERHENTI\n";
|
||||
pesan += "Alasan: Tangki Penuh\n";
|
||||
pesan += "Level Air: " + String(waterValue);
|
||||
kirimTelegram(pesan);
|
||||
}
|
||||
|
||||
|
||||
// Jika mode otomatis aktif dan belum penuh → pompa nyala
|
||||
if (!manualPump && pompaOtomatisAktif && waterValue < WATER_FULL) {
|
||||
if (!pumpAktif) {
|
||||
digitalWrite(RELAY_PUMP, LOW);
|
||||
pumpAktif = true;
|
||||
waktuPompaBerubah = millis();
|
||||
Blynk.virtualWrite(V2, 1);
|
||||
String pesan = "🚰 POMPA OTOMATIS MENYALA\n";
|
||||
pesan += "Alasan: Air Habis\n";
|
||||
pesan += "Level Air: " + String(waterValue);
|
||||
kirimTelegram(pesan);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== AUTO OFF POMPA + RESET MANUAL =====
|
||||
if (pumpAktif && !pompaOtomatisAktif && millis() - waktuPompaBerubah >= POMPA_AUTO_OFF) {
|
||||
|
||||
digitalWrite(RELAY_PUMP, HIGH);
|
||||
pumpAktif = false;
|
||||
Blynk.virtualWrite(V2, 0);
|
||||
|
||||
String pesan = "⏱ POMPA DIMATIKAN OTOMATIS\n";
|
||||
pesan += "Alasan: Timeout 10 detik\n";
|
||||
pesan += "Level Air: " + String(waterValue);
|
||||
kirimTelegram(pesan);
|
||||
}
|
||||
|
||||
// reset manual setelah 10 detik
|
||||
if (manualPump && millis() - waktuPompaBerubah > 10000) {
|
||||
manualPump = false;
|
||||
}
|
||||
|
||||
// ===========================
|
||||
}
|
||||
|
||||
/***************** FUNGSI BACA DHT *****************/
|
||||
void bacaDHT() {
|
||||
|
||||
float suhuTotal = 0;
|
||||
int berhasil = 0;
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
|
||||
float t = dht.readTemperature();
|
||||
|
||||
if (!isnan(t)) {
|
||||
suhuTotal += t;
|
||||
berhasil++;
|
||||
}
|
||||
|
||||
vTaskDelay(250 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
if (berhasil > 0) {
|
||||
|
||||
lastSuhu = suhuTotal / berhasil;
|
||||
dhtValid = true;
|
||||
|
||||
Blynk.virtualWrite(V0, lastSuhu);
|
||||
|
||||
} else {
|
||||
|
||||
dhtValid = false;
|
||||
|
||||
Serial.println("❌ Gagal membaca DHT22");
|
||||
}
|
||||
}
|
||||
/***************** FUNGSI WATER SENSOR (TAMBAHAN) *****************/
|
||||
void bacaWaterSensor() {
|
||||
|
||||
// ===== AMBIL BEBERAPA SAMPLE =====
|
||||
long totalADC = 0;
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
totalADC += analogRead(WATER_SENSOR_PIN);
|
||||
vTaskDelay(2 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
// ===== RATA-RATA ADC =====
|
||||
int rawValue = totalADC / 10;
|
||||
|
||||
// ===== FILTER SMOOTHING =====
|
||||
// lebih stabil dan tidak loncat-loncat
|
||||
waterFiltered = (0.8 * waterFiltered) + (0.2 * rawValue);
|
||||
|
||||
waterValue = (int)waterFiltered;
|
||||
|
||||
// ===== KALIBRASI SENSOR =====
|
||||
// sesuaikan hasil Serial Monitor
|
||||
int adcKosong = 250;
|
||||
int adcPenuh = 950;
|
||||
|
||||
waterPercent = map(waterValue, adcKosong, adcPenuh, 0, 100);
|
||||
waterPercent = constrain(waterPercent, 0, 100);
|
||||
|
||||
// ===== HYSTERESIS =====
|
||||
// supaya status tidak berubah-ubah
|
||||
|
||||
static bool statusAir = false;
|
||||
|
||||
// baru dianggap ADA AIR jika >5%
|
||||
if (waterPercent >= 5) {
|
||||
statusAir = true;
|
||||
}
|
||||
|
||||
// baru dianggap KOSONG jika <5%
|
||||
if (waterPercent <= 5) {
|
||||
statusAir = false;
|
||||
}
|
||||
|
||||
adaAir = statusAir;
|
||||
|
||||
// ===== STATUS FULL =====
|
||||
static bool statusFull = false;
|
||||
|
||||
if (waterPercent >= 100) {
|
||||
statusFull = true;
|
||||
}
|
||||
|
||||
if (waterPercent <= 90) {
|
||||
statusFull = false;
|
||||
}
|
||||
|
||||
// ===== KIRIM KE BLYNK =====
|
||||
static unsigned long lastBlynk = 0;
|
||||
|
||||
if (millis() - lastBlynk >= 500) {
|
||||
|
||||
Blynk.virtualWrite(V12, waterPercent);
|
||||
|
||||
if (statusFull) {
|
||||
Blynk.virtualWrite(V13, "FULL");
|
||||
}
|
||||
else if (adaAir) {
|
||||
Blynk.virtualWrite(V13, "AIR TERSEDIA");
|
||||
}
|
||||
else {
|
||||
Blynk.virtualWrite(V13, "KOSONG");
|
||||
}
|
||||
|
||||
lastBlynk = millis();
|
||||
}
|
||||
|
||||
// ===== SERIAL MONITOR =====
|
||||
static unsigned long lastSerial = 0;
|
||||
|
||||
if (millis() - lastSerial >= 500) {
|
||||
|
||||
// Serial.print("RAW ADC : ");
|
||||
// Serial.print(rawValue);
|
||||
|
||||
// Serial.print(" | FILTER : ");
|
||||
// Serial.print(waterValue);
|
||||
|
||||
// Serial.print(" | LEVEL : ");
|
||||
// Serial.print(waterPercent);
|
||||
|
||||
// Serial.print("% | STATUS : ");
|
||||
|
||||
if (adaAir) {
|
||||
// Serial.println("ADA AIR");
|
||||
} else {
|
||||
// Serial.println("KOSONG");
|
||||
}
|
||||
|
||||
lastSerial = millis();
|
||||
}
|
||||
}
|
||||
|
||||
/***************** LOG Feeding *****************/
|
||||
void kirimLogFeeding(String status) {
|
||||
|
||||
DateTime now = rtc.now();
|
||||
|
||||
char waktu[20];
|
||||
sprintf(waktu, "%02d:%02d:%02d",
|
||||
now.hour(),
|
||||
now.minute(),
|
||||
now.second());
|
||||
|
||||
String logFeeding = String(waktu) + " - " + status;
|
||||
|
||||
Blynk.virtualWrite(V14, logFeeding);
|
||||
}
|
||||
|
||||
|
||||
/***************** AUTO FEEDING *****************/
|
||||
void autoFeedingRTC() {
|
||||
|
||||
// === MULAI FEEDING SETIAP 1 MENIT ===
|
||||
if (!servoAutoAktif && millis() - lastFeedingTime >= INTERVAL_FEEDING) {
|
||||
|
||||
servo.write(80);
|
||||
updateServoStatus(80);
|
||||
servoAutoMulai = millis();
|
||||
servoAutoAktif = true;
|
||||
lastFeedingTime = millis();
|
||||
|
||||
String pesan = "🐹 AUTO FEEDING DIMULAI\n";
|
||||
pesan += "Interval: 1 Menit\n";
|
||||
pesan += "Status: Pakan Dibuka\n";
|
||||
|
||||
kirimTelegram(pesan);
|
||||
// Blynk.virtualWrite(V9, "Auto Feeding Dimulai");
|
||||
kirimLogFeeding("Auto Feeding Dimulai");
|
||||
}
|
||||
|
||||
// === TUTUP SETELAH DURASI ===
|
||||
if (servoAutoAktif && millis() - servoAutoMulai >= DURASI_BUKA_SERVO) {
|
||||
|
||||
servo.write(0);
|
||||
updateServoStatus(0);
|
||||
servoAutoAktif = false;
|
||||
|
||||
String pesan = "🐹 AUTO FEEDING SELESAI\n";
|
||||
pesan += "Status: Pakan Ditutup\n";
|
||||
|
||||
kirimTelegram(pesan);
|
||||
// Blynk.virtualWrite(V9, "Auto Feeding Selesai");
|
||||
kirimLogFeeding("Auto Feeding Selesai");
|
||||
}
|
||||
}
|
||||
void kirimSuhuTelegram() {
|
||||
if (dhtValid) {
|
||||
String pesan = "🌡 Update Suhu:\n";
|
||||
pesan += "Suhu saat ini: ";
|
||||
pesan += String(lastSuhu);
|
||||
pesan += " °C";
|
||||
|
||||
kirimTelegram(pesan);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue