아!두이노, 라즈베리, 코딩 노가다

기울기 체크를 데몬으로 실행하는 코드 본문

오락실

기울기 체크를 데몬으로 실행하는 코드

아이스뭐라카노 2026. 5. 20. 12:58
반응형

 

import socket
import time
import re
import RPi.GPIO as GPIO  # 환경에 맞는 GPIO 라이브러리 사용

# ==========================================
# 1. 환경 설정
# ==========================================
TILT_PIN = 18          # 기울기 센서 연결 PIN
RA_IP = "127.0.0.1"
RA_PORT = 55355        # 레트로아크가 듣는 포트
MY_DAEMON_PORT = 55356 # 외부에서 이 데몬에게 상태를 물어볼 포트 (추가)
STABLE_DURATION = 0.5  # 상태 유지 필수 시간 (0.5초)

# GPIO 초기화
GPIO.setmode(GPIO.BCM)
GPIO.setup(TILT_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)

# [소켓 1] 레트로아크 상태 조회용 클라이언트 소켓
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(0.3)

# [소켓 2] 외부 프로그램의 질문에 답장해줄 데몬 서버 소켓 (추가)
daemon_server_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
daemon_server_sock.bind(("127.0.0.1", MY_DAEMON_PORT))
daemon_server_sock.setblocking(False)  # 중요: 요청이 없어도 루프가 멈추지 않게 논블로킹 설정

# ==========================================
# 2. 상태 제어 변수 (핵심)
# ==========================================
# 현재 확정된 하드웨어 상태 (True: 90도 기울어짐 / False: 0도 평평함)
current_stable_state = (GPIO.input(TILT_PIN) == GPIO.LOW)

# 디바운싱용 추적 변수
last_raw_reading = current_stable_state  # 마지막으로 읽은 날것의 값
state_changed_time = time.time()         # 값이 처음 바뀐 시점 기록


# ==========================================
# 3. 비즈니스 로직 함수
# ==========================================
def execute_tilt_change(emulator, angle, rom_name):
    """
    기울기 변화가 최종 확정되었을 때 호출되는 함수
    전달 인수: 에뮬레이터(코어), 각도(0 또는 90), 롬파일 이름
    """
    print("\n" + "="*50)
    print("🎯 [기울기 변경 이벤트 실행]")
    print(f"▶ 롬파일 이름 : {rom_name}")
    print(f"▶ 에뮬레이터  : {emulator}")
    print(f"▶ 변경된 각도 : {angle}도")
    print("="*50 + "\n")
    
    # ------------------------------------------------------------------
    # [여기에 실제 화면 회전 명령이나 모터 제어, 베젤 교체 코드를 넣으세요]
    # 예: os.system(f"display_rotate_command --angle {angle}") 등
    # ------------------------------------------------------------------


def get_retroarch_info():
    """
    레트로아크 상태를 조회하여 (게임중여부, 코어명, 롬파일명)을 반환
    """
    try:
        sock.sendto(b"GET_STATUS\n", (RA_IP, RA_PORT))
        data, _ = sock.recvfrom(1024)
        status_str = data.decode('utf-8').strip()
        
        # 레트로아크 응답 포맷 예시: "GET_STATUS PLAYING,fbneo,내게임롬파일.zip"
        if "PLAYING" in status_str:
            # 정규식이나 분할(split)을 이용해 코어와 파일명 추출
            parts = status_str.split(',')
            if len(parts) >= 3:
                emulator = parts[1]                     # 예: fbneo
                rom_path = parts[2]                     # 예: /roms/arcade/sf2.zip
                rom_name = rom_path.split('/')[-1]      # 파일명만 추출 (sf2.zip)
                return True, emulator, rom_name
                
    except (socket.timeout, ConnectionRefusedError):
        pass
    return False, None, None


# ==========================================
# 4. 메인 루프 (데몬 작동)
# ==========================================
print("안정화 및 외부 조회(UDP) 기능 포함 기울기 감시 데몬 가동 중...")

try:
    while True:
        # ------------------------------------------------------------------
        # [추가 기능] 외부에서 데몬에게 "너 지금 몇도야?" 라고 물어봤는지 체크
        # ------------------------------------------------------------------
        try:
            message, client_address = daemon_server_sock.recvfrom(1024)
            if message.decode('utf-8').strip() == "GET_ANGLE":
                # 현재 확정된 stable 상태를 기준으로 90 또는 0을 답장합니다.
                response = "90" if current_stable_state else "0"
                daemon_server_sock.sendto(response.encode('utf-8'), client_address)
        except BlockingIOError:
            # 대기 중인 외부 메시지가 없으면 에러를 뱉지 않고 그냥 통과합니다 (논블로킹 핵심)
            pass

        # ------------------------------------------------------------------
        # 기존 기울기 센서 감시 및 안정화 로직
        # ------------------------------------------------------------------
        # 레트로아크가 게임 구동 중일 때만 기울기 로직 전반을 작동시킵니다.
        is_playing, emulator_name, current_rom = get_retroarch_info()
        
        if is_playing:
            # 센서 물리 값 읽기 (Low=기울어짐=True, High=평평함=False)
            raw_reading = (GPIO.input(TILT_PIN) == GPIO.LOW)
            now = time.time()
            
            # [경우의 수 A] 센서의 날것 값이 방금 막 바뀌었을 때 (진동 시작 혹은 전환 시작)
            if raw_reading != last_raw_reading:
                last_raw_reading = raw_reading
                state_changed_time = now  # 타이머 리셋 (여기서부터 버텨야 함)
                
            # [경우의 수 B] 센서 값이 바뀐 채로 흔들림 없이 설정한 시간(0.5초) 이상 유지되었을 때
            elif (now - state_changed_time) >= STABLE_DURATION:
                
                # 그 유지된 값이 '기존에 최종 확정되어 있던 상태'와 다르다면 수락!
                if raw_reading != current_stable_state:
                    current_stable_state = raw_reading  # 최종 상태 교체
                    
                    # 수락된 최종 상태에 따라 전달할 각도 매핑
                    final_angle = 90 if current_stable_state else 0
                    
                    # 최종 확정된 인수를 들고 대상 함수 실행
                    execute_tilt_change(emulator_name, final_angle, current_rom)
                    
        else:
            # 게임 중이 아닐 때는 센서 타이머와 이전 상태를 초기화하여 
            # 게임 진입 직후 오작동하는 것을 방지합니다.
            last_raw_reading = (GPIO.input(TILT_PIN) == GPIO.LOW)
            state_changed_time = time.time()

        # CPU 점유율 과부하 방지 (약 30Hz 감시)
        time.sleep(0.03)

except KeyboardInterrupt:
    print("\n데몬 프로그램을 종료합니다.")
finally:
    GPIO.cleanup()

 

실행가는한 속성

chmod +x /home/pi/tilt_stable_daemon.py

 

sudo systemctl status tilt-stable.service
    ```
*   **실시간 인수 전달 로그 모니터링 (가장 유용):**
    코드가 정상 작동하여 0.5초간 안정화된 후 `execute_tilt_change` 함수가 실행되면 터미널에 프린트문이 찍힙니다. 이를 실시간으로 보려면 아래 명령어를 켜두고 테스트하시면 됩니다.
    
```bash
    sudo journalctl -u tilt-stable.service -f
    ```
*   **데몬 코드 수정 후 재시작할 때:**
    파이썬 코드(`tilt_stable_daemon.py`)를 수정하셨다면, 데몬을 한 번 껐다 켜주어야 수정본이 반영됩니다.
    
```bash
    sudo systemctl restart tilt-stable.service
    ```

```</Step></Sequence>

 

sudo nano /etc/systemd/system/tilt-stable.service
    ```
  
   Ctrl+X)">
    열린 나노(nano) 편집기에 아래 내용을 그대로 복사해서 붙여넣습니다. 
    *(만약 라즈베리파이 사용자 이름이 `pi`가 아니거나 파이썬 파일 경로가 다르다면 `User`, `WorkingDirectory`, `ExecStart` 부분을 본인 환경에 맞게 수정해주세요.)*

 

 

[Service]
Type=simple
User=pi
WorkingDirectory=/home/pi
# 실행할 파이썬 절대경로와 스크립트 파일 지정
ExecStart=/usr/bin/python3 /home/pi/tilt_stable_daemon.py
# 프로그램이 예기치 않게 죽더라도 5초 후에 자동으로 부활시키는 핵심 설정
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

 

 

ni
[Unit]
Description=RetroArch Tilt Sensor Stable Monitor Daemon
# 네트워크와 오디오, 기본 시스템이 완전히 준비된 후에 데몬이 켜지도록 보장합니다.
After=network.target sound.target

[Service]
Type=simple
User=pi
WorkingDirectory=/home/pi
# 실행할 파이썬 절대경로와 스크립트 파일 지정
ExecStart=/usr/bin/python3 /home/pi/tilt_stable_daemon.py
# 프로그램이 예기치 않게 죽더라도 5초 후에 자동으로 부활시키는 핵심 설정
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

방금 만든 설정 파일을 리눅스 시스템(systemd)에 인식시키고, 기기가 부팅될 때마다 자동으로 켜지도록 등록(Enable)합니다.

bash
sudo systemctl daemon-reload
sudo systemctl enable tilt-stable.service

이제 새로 만든 데몬 서비스를 지금 바로 백그라운드에서 실행시킵니다.

bash
sudo systemctl start tilt-stable.service