modify: DI 输入监听
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user