TKK_E32230176/Source-Code-Sistem/ac_control.py

305 lines
10 KiB
Python

#!/usr/bin/env python3
"""
AC Control System untuk AC Panasonic
Mengontrol AC berdasarkan deteksi kehadiran (AUTO) atau manual via MQTT
"""
import paho.mqtt.client as mqtt
from paho.mqtt import client as mqtt_client
import subprocess
import os
import json
import time
import signal
import sys
import threading
from datetime import datetime
# ====================== KONFIGURASI ======================
MQTT_BROKER = "localhost"
MQTT_PORT = 1883
CONTROL_TOPIC = "classroom/ac/control"
STATUS_TOPIC = "classroom/ac/status"
PRESENCE_TOPIC = "classroom/presence"
# GPIO untuk IR
TRANSMITTER_GPIO = 18
# File konfigurasi
IRRP_SCRIPT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "irrp.py")
JSON_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ac_codes.json")
LOG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ac_control.log")
# State variables
AUTO_MODE = True
CURRENT_AC_STATE = "off"
# Timer Delay
DELAY_TIMER_MINUTES = 5
DELAY_TIMER_ACTIVE = False
DELAY_TIMER_THREAD = None
# ====================== LOGGING ======================
def log_message(message, level="INFO"):
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_entry = f"[{timestamp}] [{level}] {message}"
print(log_entry)
try:
with open(LOG_FILE, "a") as f:
f.write(log_entry + "\n")
except:
pass
# ====================== FUNGSI KIRIM STATUS ======================
def publish_status(client, reason=""):
"""Kirim status AC ke MQTT"""
status_message = {
"ac_state": CURRENT_AC_STATE,
"auto_mode": AUTO_MODE,
"delay_active": DELAY_TIMER_ACTIVE,
"delay_remaining": DELAY_TIMER_MINUTES * 60 if DELAY_TIMER_ACTIVE else 0,
"reason": reason,
"timestamp": datetime.now().isoformat()
}
client.publish(STATUS_TOPIC, json.dumps(status_message))
log_message(f"📤 Status dikirim: {CURRENT_AC_STATE} (reason: {reason})")
# ====================== IR CONTROL ======================
def send_ir(key):
log_message(f"Mengirim IR: {key}")
if not os.path.exists(JSON_FILE):
log_message(f"File {JSON_FILE} tidak ditemukan!", "ERROR")
return False
try:
with open(JSON_FILE, 'r') as f:
codes = json.load(f)
if key not in codes:
log_message(f"Key '{key}' tidak ditemukan di JSON", "ERROR")
return False
except Exception as e:
log_message(f"Gagal baca JSON: {e}", "ERROR")
return False
cmd = ["python3", IRRP_SCRIPT, "-p", "-g", str(TRANSMITTER_GPIO), "-f", JSON_FILE, key]
try:
subprocess.run(cmd, check=True, capture_output=True, text=True, timeout=10)
log_message(f"IR berhasil dikirim: {key}")
return True
except Exception as e:
log_message(f"Gagal kirim IR: {e}", "ERROR")
return False
def set_ac_state(state, client=None, reason=""):
global CURRENT_AC_STATE
if state == "on" and CURRENT_AC_STATE == "off":
log_message("Menyalakan AC...")
if send_ir("POWER_ON"):
CURRENT_AC_STATE = "on"
if client:
publish_status(client, reason or "power_on")
return True
return False
elif state == "off" and CURRENT_AC_STATE == "on":
log_message("Mematikan AC...")
if send_ir("POWER_OFF"):
CURRENT_AC_STATE = "off"
if client:
publish_status(client, reason or "power_off")
return True
return False
return False
# ====================== DELAY TIMER ======================
def start_delay_timer(client):
global DELAY_TIMER_ACTIVE, DELAY_TIMER_THREAD
if DELAY_TIMER_ACTIVE:
log_message("Delay timer sudah aktif")
return
DELAY_TIMER_ACTIVE = True
log_message(f"⏳ Delay timer dimulai: {DELAY_TIMER_MINUTES} menit")
# Kirim status timer aktif
publish_status(client, "delay_started")
def timer_worker():
global DELAY_TIMER_ACTIVE, CURRENT_AC_STATE
total_seconds = DELAY_TIMER_MINUTES * 60
for remaining in range(total_seconds, -1, -1):
if not DELAY_TIMER_ACTIVE:
log_message("Delay timer dibatalkan")
return
if remaining > 0:
# Update setiap 10 detik
if remaining % 10 == 0 or remaining <= 5:
status_msg = {
"ac_state": CURRENT_AC_STATE,
"auto_mode": AUTO_MODE,
"delay_active": True,
"delay_remaining": remaining
}
client.publish(STATUS_TOPIC, json.dumps(status_msg))
minutes = remaining // 60
seconds = remaining % 60
log_message(f"Sisa waktu: {minutes:02d}:{seconds:02d}")
time.sleep(1)
else:
# Timer habis
if DELAY_TIMER_ACTIVE and CURRENT_AC_STATE == "on":
log_message("⏰ TIMER HABIS - Mematikan AC")
if send_ir("POWER_OFF"):
CURRENT_AC_STATE = "off"
publish_status(client, "delay_timer_expired")
DELAY_TIMER_ACTIVE = False
return
DELAY_TIMER_THREAD = threading.Thread(target=timer_worker, daemon=True)
DELAY_TIMER_THREAD.start()
def cancel_delay_timer(client):
global DELAY_TIMER_ACTIVE
if DELAY_TIMER_ACTIVE:
DELAY_TIMER_ACTIVE = False
log_message("Delay timer dibatalkan")
publish_status(client, "delay_cancelled")
# ====================== MQTT HANDLERS ======================
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code == 0:
log_message("✅ MQTT terhubung ke broker")
client.subscribe([(CONTROL_TOPIC, 0), (PRESENCE_TOPIC, 0)])
log_message(f"Subscribed ke: {CONTROL_TOPIC} & {PRESENCE_TOPIC}")
# Kirim status awal
publish_status(client, "initial_connection")
else:
log_message(f"❌ MQTT connect gagal: {reason_code}", "ERROR")
def on_message(client, userdata, msg):
global AUTO_MODE, CURRENT_AC_STATE, DELAY_TIMER_MINUTES
topic = msg.topic
payload = msg.payload.decode().strip()
log_message(f"MQTT → {topic} : {payload}")
# ==================== PRESENCE (AUTO MODE) ====================
if topic == PRESENCE_TOPIC:
if not AUTO_MODE:
log_message("Mode MANUAL aktif → ignore presence")
return
presence = payload.lower()
if presence == "ada":
log_message("👥 Ada orang terdeteksi (AUTO)")
cancel_delay_timer(client)
if CURRENT_AC_STATE == "off":
log_message("🔛 Menyalakan AC otomatis...")
set_ac_state("on", client, "presence_detected")
elif presence == "tidak ada":
log_message("🚪 Tidak ada orang (AUTO)")
if CURRENT_AC_STATE == "on" and not DELAY_TIMER_ACTIVE:
log_message(f"⏳ Memulai delay timer {DELAY_TIMER_MINUTES} menit...")
start_delay_timer(client)
# ==================== CONTROL (DARI WEB) ====================
elif topic == CONTROL_TOPIC:
payload_lower = payload.lower()
if payload_lower == "auto_on":
AUTO_MODE = True
cancel_delay_timer(client)
log_message("🤖 Mode AUTO diaktifkan")
publish_status(client, "auto_mode_on")
elif payload_lower == "auto_off":
AUTO_MODE = False
cancel_delay_timer(client)
log_message("👤 Mode MANUAL diaktifkan")
publish_status(client, "manual_mode_on")
elif payload_lower.startswith("delay_"):
try:
minutes = int(payload_lower.split("_")[1])
if 1 <= minutes <= 60:
DELAY_TIMER_MINUTES = minutes
log_message(f"⚙️ Delay timer diubah menjadi {minutes} menit")
publish_status(client, "delay_changed")
except:
pass
elif payload_lower == "on":
log_message("👤 Manual: Menyalakan AC")
cancel_delay_timer(client)
set_ac_state("on", client, "manual_on")
elif payload_lower == "off":
log_message("👤 Manual: Mematikan AC")
cancel_delay_timer(client)
set_ac_state("off", client, "manual_off")
elif payload_lower == "status":
publish_status(client, "manual_request")
# ====================== SIGNAL HANDLER ======================
def signal_handler(sig, frame):
log_message("Menerima sinyal shutdown...")
sys.exit(0)
# ====================== MAIN ======================
if __name__ == "__main__":
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
log_message("=" * 70)
log_message("🚀 AC CONTROL SYSTEM - PANASONIC")
log_message("=" * 70)
# Tampilkan kode IR yang tersedia
if os.path.exists(JSON_FILE):
try:
with open(JSON_FILE, 'r') as f:
codes = json.load(f)
log_message(f"Kode IR tersedia: {list(codes.keys())}")
except Exception as e:
log_message(f"Gagal baca ac_codes.json: {e}", "ERROR")
else:
log_message(f"⚠️ File {JSON_FILE} tidak ditemukan!", "WARNING")
log_message(" Jalankan record terlebih dahulu: python3 rcd.py POWER_ON")
# Status awal
log_message(f"Mode awal : {'AUTO' if AUTO_MODE else 'MANUAL'}")
log_message(f"AC : {CURRENT_AC_STATE}")
log_message(f"Delay Timer : {DELAY_TIMER_MINUTES} menit")
log_message(f"Log file : {LOG_FILE}")
# MQTT Client
try:
client = mqtt.Client(mqtt_client.CallbackAPIVersion.VERSION2)
client.on_connect = on_connect
client.on_message = on_message
client.connect(MQTT_BROKER, MQTT_PORT, 60)
log_message("\n✅ Sistem siap. Menunggu perintah MQTT...")
log_message(f" Subscribe ke: {CONTROL_TOPIC} & {PRESENCE_TOPIC}")
log_message(" Tekan Ctrl+C untuk berhenti\n")
client.loop_forever()
except Exception as e:
log_message(f"❌ Error MQTT: {e}", "ERROR")
sys.exit(1)