처음‎ > ‎지식기반‎ > ‎

RPi: RPino와의 heartbeat 교환

RPi에 늘솜(주)의 RPino(아두이노 프로 호환) 보드를 붙여 서로의 동작상태를 감시하는 구성입니다.

[목표]
  • RPi와 RPino가 지속적으로 heartbeat을 교환 한다.
  • 파트너에 문제가 발생(timeout)하면 하드웨어 리셋 등 추가 할 수 있는 장치를 마련 한다.
[도전]
  • 단순-무선 (not '무식')
  • UART를 통한 이기종간 동기(sync) 통신
    • 하드웨어: RPi <-> Arduino
    • 소프트웨어: Python <-> C++
  • 투명하지만 정상상태(sanity)를 충분히 반영하는 신호로 대화 한다.
  • 데비안 HA(High Availability) Heartbeat 패키지처럼 일단 실행하면 UART를 완전 점유 해서는 안된다.


RPi에 범용OS (리눅스)가 돌아가는 만큼, 이런 저런 패키지를 설치 해서 사용하다보면 간혹 뻣는(=무반응=hang=..) 상황이 발생 합니다. 온라인상에는, 웹서버용도로 사용하는데 운영하다 저절로 멈춘다는 글들이 많은데 근본적으로 비슷비슷한 원인일것 입니다. 어쨋든, 이런경우 대부분 ssh등 복구를 위한 쉘접속도 안되므로 리셋밖에는 방법이 없습니다.

멀리있는 RPi를 리셋하는것이 문제인데, 사실 RPi만 가지고, 그래도 가장 가성비가 좋은 방법이 watchdog을 사용하는 것일껍니다. 다행히 RPi의 CPU가 하드웨어 watchdog기능이 있고 해서 apt-get으로 watchdog 패키지를 설치해서 사용 하면 되는데, 역시,, 다 필요없고, 직접 리셋스위치를 눌렀다 켜는 것 만큼 직관적이고 확실한 방법은 없습니다.

RPi를 가지고 타이밍이 절대중요한 감시/제어 용도로 장시간 사용 할 수 없으므로, 전용프로세서인 아두이노와의 결혼이 의미 있는 것인데, 그 한가지 활용으로 상호감시를 구현 해 봤습니다.

운영 시나리오는:
  1. 서로 상대방이 정상 동작상태가 아니면 줄 수 없는 신호로 계속 대화하며 상대의 상태를 확인 합니다.
  2. 순간 응답이 없어도 일시적인 상황이 있을 수 있으므로 내부타이머를 돌리며 (예:30초) 기다려 줍니다.
  3. 설정최대지연시간을 지나면 상대방의 reset핀으로 연결되어 있는 각자의 DO(Digital Output) 핀으로 출력을 보내 상대를 리셋합니다.
현재는 타임아웃(2번)없이 무한정 파트너를 기다려주는 구조 인데 하드웨어 연결(3번) 부분이 준비 되면 추가할 계획입니다. (RPi의 reset핀이 revision 2부터 P6헤더로 올려져 있다고 알고 있습니다.)

이제부터 프로그램 코드입니다. 
텍스트 에디터를 사용해서 아래 코드대로 heartbeat.py를 작성하고 실행 권한을 주고 실행 시킵니다.
  • nano heartbeat.py
  • chmod +x heartbeat.py
  • ./heartbeat.py

heartbeat.py (RPi) - v0.5

#!/usr/bin/env python
import serial
import time
import subprocess

# 시리얼포트(UART) 초기화 (보레이트: 115200bps)
ser = serial.Serial('/dev/ttyS0', 115200)
ser.open()

isOn=chr(1) # 토글 스위치 준비

try:
  while 1:
    # 토글값을 아두이노로 전달 한다.
    ser.write(isOn)
    # 다음 사이클을 위해 토글값을 반전 시킨다: 0->1, 1->0
    if isOn == chr(0): isOn = chr(1)
    else: isOn = chr(0)

    # 답변이 오도록 조금(0.5초) 기다린다. 
    time.sleep(0.5)    
    # 답변을 읽어 들인다.
    response = ser.read()

    # 재부팅시 들어오는 쓰레기값을 무시 하고,
    if response.isdigit() and int(response) < 2:
      # RPi의 온보드 LED(ACK)를 받은 답변 그대로 켜거나 끈다.
      subprocess.call('echo '+ response +' > /sys/class/leds/led0/brightness', shell=True)
# Ctrl+C 등, 키보드 인터럽트가 들어오면,
except KeyboardInterrupt:
    # 아두이노의 온보드 LED부터 끄고
    ser.write(chr(0))
    # RPi의 LED도 끄고
    subprocess.call('echo 0 > /sys/class/leds/led0/brightness', shell=True)
    # 열려있는 시리얼 포트를 닫고 끝낸다.
    ser.close()

다음은 RPino를 위한 아두이노 코드인데 Arduino for Windows로 upload하거나, 아니면 팬너골드님이 멋지게 설명해 주신 대로 RPi에서 직접 컴파일/업로드 합니다. 

heartbeat.ino (RPino) - v0.5

#define PIN_LED 13  // 온보드 LED 선언
byte received = 0;

// 시리얼포트 (UART)를 준비 시킨다. (보레이트: 115200bps)
void setup(void){
  Serial.begin(115200);
  pinMode(PIN_LED, OUTPUT);
};

void loop(void){
  // 시리얼 포트로 뭔가가 들어 오면
  while(Serial.available()){
    // 그 데이터를 읽어 들인다.
    received  = Serial.read();

    // 데이터값 그대로 온보드 LED를 켜거나 끈다.
    digitalWrite(PIN_LED, received);

    // 읽은 데이터를 그 값 그대로 RPi에 보낸다.
    Serial.print(received , DEC);
  };
};

[주의]
  • 실행전 시리얼포트를 쓰고 있는 프로그램이 있으면 모두 정지 시킨다. 예: heartbeat 사용중이라면:
    • sudo service heartbeat stop, 또는 (정상 종료가 안된다면),
    • killall heartbeat
  • 데비안 watchdog 패키지를 사용중이라면 태스트때 혼동스러우므로 이것도 정지 시킨다.
    • sudo service watchdog stop, 또는,
    • killall watchdog
  • 실행중에는 우리 프로그램(heatbeat.py)이 시리얼포트를 점유하게 되므로 scons 등이 UART를 통한 업로드에 실패한다.
    • 직접 실행중이라면 'Ctrl+C' 키를, 백그라운드 실행 중이라면,
    • killall python


[UPDATE - 08/02/213]

아래 사진은 좀 가려졌습니다만,


일전에 미뤄뒀던 timeout기능과 리셋신호 전달하는 부분을 추가하여 코드를 수정 하였습니다.

heartbeat.py (RPi) - v1.0

#!/usr/bin/env python
import serial
import time
import subprocess
import RPi.GPIO as GPIO

# 시리얼포트(UART) 초기화 (장치, 보레이트, 통신대기 타임아웃 1초)
ser = serial.Serial('/dev/ttyS0', 115200, timeout=1)
ser.open()

# GPIO 셋모드 초기화
GPIO.setmode(GPIO.BOARD)

TIMEOUT = 5 # 허용대기시간량 정의 ('5' = 5초)
timer = TIMEOUT # 타이머 준비
isOn = chr(1) # 토글 스위치 준비
# RPi의 온보드 LED(ACT) 사용 준비
subprocess.call('echo none > /sys/class/leds/led0/brightness', shell=True)

try:
  while 1:
    # 토글값을 아두이노로 전달 한다
    ser.write(isOn)
    # 다음 사이클을 위해 토글값을 반전 시킨다: 0->1, 1->0
    if isOn == chr(0): isOn = chr(1)
    else: isOn = chr(0)

    # 답변을 읽어 들인다.
    response = ser.read();

    # 재부팅시 들어오는 쓰레기값을 무시 하고 유효한 값이라면
    if response.isdigit() and int(response) < 2:
      # 받은 답변 그대로 LED를 켜거나 끈다.
      subprocess.call('echo '+ response +' > /sys/class/leds/led0/brightness', shell=True)
      # 1초간 대기 했다가
      time.sleep(1)
      # 타이머를 초기화한다.
      timer = TIMEOUT
    # 아두이노로부터 답변이 없고 (= 통신대기 타임아웃으로 왔고)
    elif response.__len__() == 0:
      # 허용대기시간을 넘겼다면
      if timer == 0:
        # 아두이노의 리셋핀에 down edge를 보내 리셋 시키고
        GPIO.setup(18, GPIO.OUT)
        GPIO.setup(18, GPIO.IN)
        # 타이머를 초기화 한다
        timer = TIMEOUT
      # 대기 카운터값을 줄인다.
      else: timer -= 1

# Ctrl+C 등, 키보드 인터럽트가 들어오면,
except KeyboardInterrupt:
    # 아두이노의 온보드 LED부터 끄고
    ser.write(chr(0))
    # RPi의 LED도 끄고
    subprocess.call('echo 0 > /sys/class/leds/led0/brightness', shell=True)
    # 원래 기능으로 되돌려 놓은 다음,
    subprocess.call('echo mmc0 > /sys/class/leds/led0/brightness', shell=True)
    # 열려있는 시리얼 포트를 닫고 끝낸다.
    ser.close()

heartbeat.ino (RPino) - v1.0

#define PIN_LED 13 // 온보드 LED 선언
#define PIN_RESET 4 // RPi의 리셋핀에 연결된 DO핀을 지정
#define TIMEOUT 30  // 허용대기시간량 정의 ('30' = 30초)
int timer = TIMEOUT;  // 타이머 준비
byte received = 0;

// 시리얼포트(UART) 초기화 (장치, 보레이트)
void setup(void){
  Serial.begin(115200);
  pinMode(PIN_LED, OUTPUT);
};

void loop(void){
  while(1){
    // 데이터를 읽어 들인다.
    received = Serial.read();
    // 유효한 heartbeat값이 들어왔다면
    if((received - '0') < 2){
      // 데이터값 그대로 온보드 LED를 켜거나 끈다.
      digitalWrite(PIN_LED, received);
      // 읽은 데이터를 그 값 그대로 RPi에 보낸다.
      Serial.print(received , DEC);
      // 타이머를 초기화한다.
      timer = TIMEOUT;
      // 다음 신호가 올때까지 대기 한다.
      while(Serial.available() <= 0){};
    // 유효하지 않은 데이터가 읽혔고
    }else{
      // 허용대기시간을 넘겼다면
      if(timer == 0){
        // RPi 리셋핀으로 down edge를 보내 리셋 시키고
        pinMode(PIN_RESET, OUTPUT);
        pinMode(PIN_RESET, INPUT);
        // 타이머를 초기화 한다.
        timer = TIMEOUT;
      // 허용지연시간이 남았다면
      }else{
        // 타이머를 줄이고
        timer--;
        // 1초간 대기 한다.
        delay(1000);
      };
    };
  };
};
Comments