#!/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)