1、什么是996人生怀疑机?
一款能够让你(爱上996,bushi)不得不996工作的、基于树莓派实现的小玩意儿。
2、为什么要做这个小玩意?
之前在Github还是哪篇博客上看过一款编辑器,这款编辑器能够提供沉浸式的编辑功能,好像是在多少分钟内没有编辑任何内容,就会自动删除已经编辑过的所有文字。这简直就是反向逼迫灵感爆发啊。
基于此,我也在思考能不能借助硬件的某些功能,实现一种类似该编辑器、逼迫创作者(打工人)反向灵感爆发的小玩意儿。于是,996人生怀疑机(以下简称小机子)就诞生了。
3、如何实现这么个小玩意?
3.1、需求梳理
想要实现这么一个小东西,我们得先从功能需求出发,梳理出想要实现的具体功能。然后针对该功能,枚举出实现过程中所需的硬件和软件资源,有了这些前置准备,我们便可以开始动手了。
【功能需求】:当前版本的功能非常简单,小机子会在每周一至周六的早上九点至晚上九点检测你是否使用键盘输入文字,如果间隔5分钟都没有输入的话,小机子的外接蜂鸣器就会开始告警,提醒你该敲代码(写文章、敲键盘)了。
【硬件需求】:树莓派板子一块(本文用的是4B)、蜂鸣器一个(本文用的是有源低电平触发)
【软件需求】:会简单python编程即可
3.2、模块架构
由于初代版本的小机子功能十分简单,因此,无论是硬件还是软件,整体架构都十分简单。
硬件架构如下图所示,只是简单的树莓派与蜂鸣器连接即可。树莓派会实时监控键盘输入,并且计算时间窗口,一旦符合告警条件,便会向蜂鸣器发送PWM信号,周期性激活蜂鸣器,使其告警。
软件架构如下图所示,大体上分为3个模块:键盘输入监控模块、时间计算模块、使能蜂鸣器模块。他们的作用分别如下所示:
- 键盘输入监控模块:监控键盘输入,一旦监听成功,通知使能模块
- 时间窗口计算模块:计算时间窗口,一旦落入我们定义的窗口(即996)内,通知使能模块
- 使能模块:根据其它两个模块的计算结果使能蜂鸣器
3.3、蜂鸣器接线
本文使用的蜂鸣器如下图所示:
- 该蜂鸣器为有源蜂鸣器,因此不需要提供晶振源,仅需要供电和触发信号即可驱动。
- 工作电压3.3V~5V,完全适配树莓派工作电压。
- 由于低电平触发,因此检测其是否工作正常非常简单,引脚全部连接完成,树莓派上电后如果发出声音代表正常工作。因为树莓派上电后引脚默认低电平。
- 3个引脚,VCC、GND和I/O引脚。分别连接树莓派的1、9、11(物理引脚,BOARD编码)三根引脚。
连接后的实物图如下所示。
3.4、代码介绍
3.4.1、键盘监控模块
键盘监控模块主要用于监听键盘输入事件,也就是key_down和key_up。初代版本为了足够简单,会对键盘上所有的键进行监听,因此在初代版本的小机子上,想要摸鱼的话还是非常简单的 ( ̄︶ ̄)↗ 。
python监控键盘事件可以使用keyboard模块,首先进行安装:
pip install keyboard
本文主要使用了keyboard模块中的hook函数,hook函数允许传递传递一个函数(可以认为是钩子),一旦检测到键盘输入事件后,便会回调该钩子函数,触发用户想要的操作。
键盘监控模块代码如下:
#!/usr/bin/env python3
import keyboard
def hook(function):
keyboard.hook(function)
def wait():
keyboard.wait()
3.4.2、蜂鸣器使能模块
该模块首先会定义两个全局变量:_buzzer_pin和_buzzer_on,前者用于标识默认激活的与蜂鸣器连接的引脚,后者用于全局标识蜂鸣器激活状态。
_buzzer_pin = 11 # 蜂鸣器物理引脚
_buzzer_on = False # 蜂鸣器是否激活
init()函数用于初始化配置、置位引脚,将指定的连接引脚设置为board、输出模式,同时置位高电平(因为使用的低电平触发蜂鸣器,高电平相对来说是0位)。
def init(pin = _buzzer_pin):
global _buzzer_pin
_buzzer_pin = pin
GPIO.setmode(GPIO.BOARD)
GPIO.setup(_buzzer_pin, GPIO.OUT)
GPIO.output(_buzzer_pin, GPIO.HIGH)
on()和off()函数用于调节引脚高低电平,同时修改蜂鸣器全局状态。
def on():
global _buzzer_on
GPIO.output(_buzzer_pin, GPIO.LOW)
_buzzer_on = True
def off():
global _buzzer_on
GPIO.output(_buzzer_pin, GPIO.HIGH)
_buzzer_on = False
loop()函数根据全局状态周期性的激活蜂鸣器,也就是让指定引脚发出指定周期的PWM波。这里的周期由时间窗口计算模块控制,后面我们便会看到。
def loop():
if _buzzer_on:
off()
else:
on()
整个模块的代码如下所示:
#!/usr/bin/env python3
import RPi.GPIO as GPIO
import time
_buzzer_pin = 11 # 蜂鸣器物理引脚
_buzzer_on = False # 蜂鸣器是否激活
def init(pin = _buzzer_pin):
global _buzzer_pin
_buzzer_pin = pin
GPIO.setmode(GPIO.BOARD)
GPIO.setup(_buzzer_pin, GPIO.OUT)
GPIO.output(_buzzer_pin, GPIO.HIGH)
def on():
global _buzzer_on
GPIO.output(_buzzer_pin, GPIO.LOW)
_buzzer_on = True
def off():
global _buzzer_on
GPIO.output(_buzzer_pin, GPIO.HIGH)
_buzzer_on = False
def loop():
if _buzzer_on:
off()
else:
on()
# 主要用于测试
def main():
init()
while True:
time.sleep(1)
loop()
if __name__ == '__main__':
main()
3.4.3、时间窗口计算模块
该模块拥有一个全局变量_pressed,用于标识是否存在键盘输入事件
# keyboard input status
_pressed = False
change_press_status()函数是一个回调函数,还记得3.4.1小节说的键盘监听hook()需要传递一个回调函数嘛。每当检测到键盘监听后,便会回调change_press_status()函数将_pressed全局状态置位True,之后检测到_pressed=True,便会重置蜂鸣器,并重新开始计时。
def change_press_status(event):
"""change global status
Once keyboard input is detected, this function will be called back,
the global status pressed will be modified, and the counting thread will recount
Args:
event: detect event, not use
Returns: none
"""
global _pressed
_pressed = True
in996Period()和detection()函数主要用于计时,前者会判断当前时间是否位于996的period内,如果是的话返回True,反之False。后者用于计算时间间隔,detection()函数维护了一个计数器count,每秒自增1。如果没有键盘输入,并且count>interval(时间间隔,单位为60s,用户可自定义),并且在996的period内,便会调用使能模块的loop()函数,激活蜂鸣器。一旦有键盘输入事件后,便会重置计数器和蜂鸣器。
def in996Period():
now = datetime.now()
weekday = now.timetuple().tm_wday
hour = now.timetuple().tm_hour
if weekday < 6 and (hour >= 9 and hour <= 21):
return True
else:
return False
def detection(interval):
global _pressed
count = 0
interval = interval * 60
while True:
time.sleep(1)
count += 1
if (not _pressed) and (count > interval) and in996Period():
buzzer.loop()
# print("输出GPIO")
elif _pressed:
_pressed = False
count = 0
buzzer.off()
main()函数是启动函数,是整个project的入口。负责初始化键盘回调、蜂鸣器使能模块,开始进行监听。
def main():
keyboard_detection.hook(change_press_status)
buzzer.setup()
detection(5)
整个模块的代码如下所示:
#!/usr/bin/env python3
import time
from datetime import datetime
import keyboard_detection
import buzzer
# keyboard input status
_pressed = False
def change_press_status(event):
"""change global status
Once keyboard input is detected, this function will be called back,
the global status pressed will be modified, and the counting thread will recount
Args:
event: detect event, not use
Returns: none
"""
global _pressed
_pressed = True
def in996Period():
now = datetime.now()
weekday = now.timetuple().tm_wday
hour = now.timetuple().tm_hour
if weekday < 6 and (hour >= 9 and hour <= 21):
return True
else:
return False
def detection(interval):
global _pressed
count = 0
interval = interval * 60
while True:
time.sleep(1)
count += 1
if (not _pressed) and (count > interval) and in996Period():
buzzer.loop()
# print("输出GPIO")
elif _pressed:
_pressed = False
count = 0
buzzer.off()
def main():
keyboard_detection.hook(change_press_status)
buzzer.setup()
detection(5)
if __name__ == '__main__':
main()
3.5、开始工作
将上述三个模块的代码放到同一个文件夹下,命名分别为:keyboard_detection.py、buzzer.py、timing.py。然后输入如下命令即可正常工作:
sudo python3 timing.py
4、展望
初代小机子目前仅实现了最最基本的功能,满足最最基本的需求(谁TM有这需求啊(~ ̄▽ ̄)~!!!)。如果大家觉得还不过瘾,摸鱼的空间还是非常大,本文还非常贴心的为大家准备了几条展望,进一步抹杀摸鱼的可能。
- 通过滑动窗口检测单位时间内的字符输入量。(摸鱼可能性 -1)
- 利用人工智能技术检测输入内容合理性。(摸鱼可能性再次 -1)
- 采用无源可调频蜂鸣器播放忐忑。(折磨程度 +10086)
5、总结
哈哈哈,妹有总结!!!以上内容仅供娱乐,如有冒犯,还请海涵。ヾ(≧▽≦*)o