Files
chongming-plugins/aibox_sophon_adapter/in1.py
T

213 lines
6.9 KiB
Python
Raw Normal View History

2026-09-11 16:55:54 +08:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""IN1 (12V 输入) 事件监听。
背景:
IN1 在硬件上接到某条 GPIO,但那条 GPIO 已被内核 gpio-keys 驱动占用,
/sys/class/gpio 导不出来。驱动把它映射到 /dev/input/event1,
上报键码 KEY_UP (103)。
本机光耦输入是反相的:施加 12V 时 GPIO 被拉低,驱动上报“释放”(0);
撤除 12V 时 GPIO 被上拉,驱动上报“按下”(1)。
因此默认 active_low=True,把语义翻回来,让“加 12V = 有电”。
本模块只做一件事:盯着 IN1,有电/断电时回调。
依赖:仅标准库。
"""
import argparse
import datetime
import glob
import os
import select
import struct
import subprocess
import sys
import time
# ---- input 事件协议 ----
EV_KEY = 0x01
EVENT_STRUCT = struct.Struct("llHHi") # aarch64 / x86_64 都是 24 字节
KEY_UP = 103
VALUE_RELEASE = 0
VALUE_PRESS = 1
VALUE_REPEAT = 2
DEFAULT_DEVICE = "/dev/input/event1"
def now():
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
def find_event_device(name="gpio-keys"):
"""按设备名找 eventN,找不到返回 None。设备号变化时很有用。"""
for path in sorted(glob.glob("/dev/input/event*")):
ev = path.rsplit("/", 1)[-1]
namefile = "/sys/class/input/%s/device/name" % ev
try:
with open(namefile) as f:
if f.read().strip() == name:
return path
except OSError:
continue
return None
class IN1Listener:
"""监听 IN1,有电/断电触发回调。
active_low=True(默认):驱动上报“释放”(0) 视为有电,“按下”(1) 视为断电。
适配本机光耦反相电路。
active_low=False: 按驱动原始语义,value=1 视为有电。
用法:
def on_press(ts): print("IN1 有电", ts)
def on_release(ts): print("IN1 断电", ts)
with IN1Listener(on_press=on_press, on_release=on_release) as lis:
lis.run() # 阻塞,Ctrl-C 退出
# lis.run(duration=10) # 跑 10 秒后返回
"""
def __init__(self, device=DEFAULT_DEVICE, key=KEY_UP,
on_press=None, on_release=None,
on_event=None, log=None,
active_low=True):
self.device = device
self.key = key
self.on_press = on_press # 12V 接通
self.on_release = on_release # 12V 撤除
self.on_event = on_event # 原始事件 (ts, code, value),可选
self.log = log or (lambda msg: print(msg, flush=True))
self.active_low = active_low
self.fd = None
self.buf = b""
# ---- 生命周期 ----
def open(self):
if self.fd is None:
self.fd = os.open(self.device, os.O_RDONLY | os.O_NONBLOCK)
def close(self):
if self.fd is not None:
try:
os.close(self.fd)
finally:
self.fd = None
def __enter__(self):
self.open()
return self
def __exit__(self, *exc):
self.close()
# ---- 事件处理 ----
def _drain(self):
if self.fd is None:
return
try:
data = os.read(self.fd, EVENT_STRUCT.size * 64)
except OSError:
return
self.buf += data
while len(self.buf) >= EVENT_STRUCT.size:
chunk, self.buf = self.buf[:EVENT_STRUCT.size], self.buf[EVENT_STRUCT.size:]
sec, usec, etype, code, value = EVENT_STRUCT.unpack(chunk)
if etype != EV_KEY:
continue
ts = sec + usec / 1e6
if self.on_event:
self.on_event(ts, code, value)
if code != self.key:
continue
if value == VALUE_REPEAT:
self.log("%s IN1 重复" % now())
continue
# 硬件极性:active_low 时,value=0 表示 12V 接通
if self.active_low:
powered = (value == VALUE_RELEASE)
else:
powered = (value == VALUE_PRESS)
if powered:
self.log("%s IN1 有电" % now())
if self.on_press:
self.on_press(ts)
else:
self.log("%s IN1 断电" % now())
if self.on_release:
self.on_release(ts)
# ---- 主循环 ----
def run(self, duration=None):
"""阻塞监听。duration 秒后返回;None = 一直跑到 Ctrl-C。"""
if self.fd is None:
self.open()
if self.fd is None:
return
poller = select.poll()
poller.register(self.fd, select.POLLIN)
deadline = time.time() + duration if duration else None
while True:
timeout = 500 # ms
if deadline:
remain = deadline - time.time()
if remain <= 0:
return
timeout = int(min(remain, 0.5) * 1000)
poller.poll(timeout)
self._drain()
# ---- CLI 演示 ----
def main():
ap = argparse.ArgumentParser(description="监听 IN1 (12V 输入)")
ap.add_argument("-d", "--device", default=DEFAULT_DEVICE,
help="事件设备,默认 %(default)s;传 auto 自动按名字查找")
ap.add_argument("-k", "--key", type=lambda s: int(s, 0), default=KEY_UP,
help="键码,默认 103 (KEY_UP)")
ap.add_argument("-t", "--duration", type=float, default=0,
help="监听秒数,0 = 一直跑")
ap.add_argument("--press-command", help="IN1 有电时执行的 shell 命令")
ap.add_argument("--release-command", help="IN1 断电时执行的 shell 命令")
ap.add_argument("--active-low", dest="active_low", action="store_true",
default=True,
help="12V 接通上报为“有电”(默认,适配本机光耦反相)")
ap.add_argument("--no-active-low", dest="active_low", action="store_false",
help="不翻转,按驱动原始语义")
args = ap.parse_args()
device = args.device
if device == "auto":
device = find_event_device("gpio-keys") or DEFAULT_DEVICE
def run_cmd(cmd):
if cmd:
subprocess.Popen(cmd, shell=True)
with IN1Listener(
device=device,
key=args.key,
active_low=args.active_low,
on_press=lambda ts: run_cmd(args.press_command),
on_release=lambda ts: run_cmd(args.release_command),
) as lis:
print("监听 %s key=%d active_low=%s,Ctrl-C 退出"
% (device, args.key, args.active_low), flush=True)
try:
lis.run(duration=args.duration or None)
except KeyboardInterrupt:
print("\n退出。")
return 0
if __name__ == "__main__":
sys.exit(main())