Files
chongming-plugins/aibox_sophon_adapter/in1.py
T
2026-09-11 16:55:54 +08:00

213 lines
6.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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())