지난 글에서 k3s 클러스터의 노트북 노드를 정전 감지기로 만들었다. 이번엔 같은 노트북에 붙어 있는 가속도계를 쓴다.

2-in-1 노트북에는 화면 자동 회전용 센서가 들어 있다. 가속도계, 자이로, 지자기, 힌지 각도 센서까지 있다. 서버로 쓰는 동안 이 센서들은 아무 일도 하지 않는다. 여기에 일을 시키면 “누가 서버를 건드렸다 / 옮겼다 / 떨어뜨렸다” 를 알 수 있다.

1. 센서는 어디에 있나 — IIO 서브시스템

리눅스는 이런 센서를 IIO(Industrial I/O) 서브시스템으로 노출한다. 노트북의 센서는 대개 HID 센서 허브를 통해 연결된다. 커널 문서(HID Sensors Framework)의 설명은 이렇다. “HID sensor framework provides necessary interfaces to implement sensor drivers, which are connected to a sensor hub.”

$ for d in /sys/bus/iio/devices/iio:device*; do cat $d/name; done
incli_3d      # 기울기
gyro_3d       # 자이로
magn_3d       # 지자기(나침반)
accel_3d      # 가속도  ← 이번 재료
dev_rotation  # 회전(쿼터니언)
hinge         # 힌지 각도

가속도 값은 in_accel_{x,y,z}_raw 에 정수로 들어 있다. IIO sysfs ABI 문서에 따르면 스케일과 오프셋을 적용한 단위가 m/s² 다(“Units after application of scale and offset are m/s^2”). 즉 raw × in_accel_scale 이 가속도다.

in_accel_x_raw = -19642     × 0.000009806 = -0.19 m/s²
in_accel_y_raw = 187392     × 0.000009806 =  1.84 m/s²
in_accel_z_raw = 976183     × 0.000009806 =  9.57 m/s²

가만히 있으면 벡터의 크기가 중력(약 9.8 m/s²)이 된다. z 가 거의 전부이니 노트북이 책상에 평평하게, 약간 기운 채 누워 있다는 뜻이다.

2. 먼저 잰다 — 노이즈 바닥과 갱신 속도

임계값을 감으로 정하면 헛알림 폭탄이 되거나 아무것도 못 잡는다. 그래서 가만히 둔 상태로 30초 동안 0.2초 간격으로 샘플을 모았다.

측정 값
샘플 수 131
평균 벡터 (−0.194, 1.835, 9.570) m/s²
평균으로부터의 편차 p50 / p99 / 최대 0.0065 / 0.0151 / 0.0152 m/s²

정지 노이즈는 최대 0.015 m/s² 다. 충격 임계값은 그 약 25배인 0.4 m/s²(약 0.04g) 로 잡았다. 실제로 60초 동안 돌려 보니 헛알림이 없었다.

갱신 속도도 확인했다. in_accel_sampling_frequency 는 10(Hz)이었다. 그리고 설명하지 못한 현상이 하나 있었다.

  • 처음에 0.05초 간격으로 100번 연속 읽었다. 읽기 자체가 느려서 8초가 걸렸는데, 값이 100번 모두 같았다.
  • 0.3초 간격으로 20번 읽었을 때는 16번이 달랐다.
  • 0.2초 간격으로 30초간 읽은 위 측정에서도 값이 계속 변했다.

8초 동안 값이 전혀 안 변한 이유는 확인하지 못했다. 센서 허브가 너무 촘촘한 요청에 캐시값을 돌려줬을 수도 있다. 다만 추정이다. 확실한 건 0.2초 간격에서는 값이 제대로 갱신된다는 것이다. 그래서 폴링 간격을 0.2초로 정했다. 폴링 간격을 정하기 전에 “값이 실제로 바뀌는가” 부터 확인하라는 게 이 일의 교훈이다.

3. 감지 로직 — 벡터 두 개로 네 가지를 판별

기준 벡터(가만히 있을 때의 중력 방향)를 하나 들고, 매 샘플을 여기에 비교한다.

이벤트 판정 의미
📳 충격 |a − 기준| > 0.4 m/s² 두드림, 책상 울림, 부딪힘
🧭 자세 변화 기준과의 각도 ≥ 8° 가 5초 이상 유지 들렸거나 옮겨짐
📍 새 자리 기울어진 상태로 10초간 조용함 새 위치에 놓임 → 기준 재설정
⚠️ 낙하 의심 |a| < 3 m/s² 자유낙하에 가까움(중력이 “사라짐”)

각도는 두 벡터의 내적으로 구한다.

def angle(a, b):
    c = sum(x * y for x, y in zip(a, b)) / (hypot(*a) * hypot(*b))
    return degrees(acos(max(-1.0, min(1.0, c))))

낙하 판정이 재미있다. 가속도계는 중력 자체를 재는 게 아니라 “떠받치는 힘” 을 잰다. 그래서 자유낙하 중에는 크기가 0 에 가까워진다. 책상 위에서는 9.8 인 값이 떨어지는 순간 뚝 떨어진다.

알림 설계 — 묶고, 합치고, 기준을 옮긴다

  • 연속 샘플은 한 건으로: 한 번 부딪히면 여러 샘플이 임계값을 넘는다. 직전 샘플이 조용했을 때만 충격 횟수를 올린다.
  • 2분 쿨다운: 알림은 2분에 한 번만 보내고, 그 사이 사건은 “충격 3회 (최대 0.9g)” 처럼 합쳐서 보낸다.
  • 기준 재설정: 노트북을 옮겨 놓으면 새 자세가 기준과 계속 다르다. 기준을 옮기지 않으면 영원히 “기울어짐” 상태가 된다. 그래서 기울어진 채 10초간 조용하면 그 자세를 새 기준으로 삼는다.
  • 정전 대비 큐: 정전 감지기와 같은 텔레그램 전송 모듈을 쓴다. 보내지 못한 알림은 쌓아 뒀다가 네트워크가 돌아오면 보낸다. 이번에 이 부분을 공용 모듈로 분리했다.

4. 흔들지 않고 테스트하기 — 샘플 재생

서버를 실제로 떨어뜨려 볼 수는 없다. 그래서 샘플 재생 모드를 만들었다. x,y,z 가 한 줄씩 든 CSV 를 넣으면 실제 센서 대신 그 값을 읽는다.

처음에는 재생이 제대로 되지 않았다. 재생은 순식간에 끝나는데, 로직은 “5초 유지”, “10초 조용”, “2분 쿨다운” 처럼 시간에 의존하기 때문이다. 벽시계 대신 샘플마다 0.2초씩 가는 가상 시계를 쓰도록 바꾸자 해결됐다. 시간 의존 로직을 테스트하려면 시계를 주입할 수 있어야 한다는 흔한 교훈을 다시 확인했다.

재생 시나리오는 이렇게 만들었다. 정지 노이즈는 실측과 비슷한 가우시안(σ=0.005)으로 넣었다.

구간 넣은 값 결과
15초 정지 기준 + 노이즈 알림 없음
두드림 2회 순간 +1.2 / −0.9 m/s² 충격 알림
20° 기울임 후 유지 회전된 중력 벡터 충격 → 🧭 자세 변화 → 📍 새 기준
순간 낙하 |a| ≈ 1 m/s² ⚠️ 낙하 의심 + 충격

기울이는 순간 “충격” 이 먼저 가고 “자세 변화” 가 뒤따르는 건 의도한 동작이다. 실제로 들어 올리면 먼저 흔들리고, 그다음 기울기가 유지되기 때문이다.

5. 상시 실행과 비용

systemd user 서비스(Restart=always, Nice=10)로 돌린다. 0.2초마다 sysfs 파일 세 개를 읽는 것이 전부라 CPU 약 1%, 메모리 약 20MB 다. 파일 핸들은 열어 둔 채 seek(0) 으로 다시 읽어서, 매번 여닫는 비용을 없앴다.

한계 — 할 수 있는 것과 없는 것

  • 짧은 충격을 놓칠 수 있다. 센서가 10Hz 이고 0.2초마다 읽으므로, 그 사이에 끝나는 아주 짧은 ‘톡’ 은 보이지 않는다. 더 촘촘히 보려면 sysfs 폴링 대신 IIO 버퍼(/dev/iio:deviceX)로 연속 스트림을 받아야 한다. 다만 이 장치는 버퍼가 이미 켜져 있고(buffer0/enable = 1) 시스템 센서 서비스(iio-sensor-proxy)도 돌고 있다. 그래서 다른 소비자와 부딪히지 않도록 이번에는 설정을 건드리지 않고 읽기만 하는 방식을 택했다.
  • 지진계가 아니다. 소비자 노트북 센서로 절대 진동량을 재는 건 무리다. “평소와 다르다” 를 잡는 도구다.
  • 뚜껑을 열고 쓰면 당연히 울린다. 사람이 쓰는 기기라면 “사용 중” 을 구분하는 로직이 필요하다. 힌지 센서나 뚜껑 상태와 조합하면 된다.
  • 실물 테스트는 아직이다. 이 글을 쓰는 시점까지 실제 두드림으로 알림이 온 기록은 없다. 재생 테스트와 60초 무알림 확인까지만 했다. 실제 책상 두드림에서 0.4 m/s² 가 적절한지는 실측으로 다시 조정할 예정이다.

정리

  • 노트북 서버에는 쓰이지 않는 센서가 여럿 있다. 리눅스는 이것들을 파일로 노출한다(IIO sysfs). 읽기만 하면 IoT 센서가 된다.
  • 임계값은 노이즈 바닥을 먼저 재고 그 배수로 정한다. 감으로 정하지 않는다.
  • 폴링 간격을 정하기 전에 값이 실제로 갱신되는지 먼저 확인한다.
  • 시간 의존 로직은 가상 시계로 테스트한다.

다음 후보는 지자기 센서다. 문에 자석을 붙이면 리드 스위치 없는 문열림 센서가 된다.

References