0%

一、尝试

树莓派通过hdmi连接了一块1920*1080的屏幕,平时时竖屏放置,用来显示日历和其他一些信息,树莓派7*24小时运行, 导致屏幕一直处于工作状态,所以我打算只在一定的时间内进行显示输出

在百度上搜索了如何休眠之类的方法,找到可以通过xset的相关命令使树莓派进入休眠或是唤醒

// 强制进入休眠
sudo xset dpms force off
// 强制唤醒
sudo xset dpms force on

但是在我尝试强制进入休眠时,屏幕只是短暂黑了下屏,再次恢复正常,并不行,不知道哪里出了问题,尝试重新写入系统镜像,用一个新系统试下

新系统启动后屏幕变回了默认的横屏模式,神奇的事发生了,这时再执行强制进入休眠,竟然成功了,当鼠标移动可以正常唤醒,可是当我重启设置了启动时,旋转90deg时,休眠命令再次失效了,多次在横屏竖屏切换尝试了几次,竖屏下都不行,只有横屏下休眠命令可以正常工作,看来是个bug(似乎之前竖屏下,自动休眠就没有正常工作过,应该也是这个原因导致的)

虽然在横屏模式下可以实现命令休眠,但是发现屏幕并没有熄灭,好像只是换了一个黑色的屏保,这点与预期有差距,我们是想让显示屏熄灭,进入待机状态

之后又百度了很久,没有发现什么好的办法。只得转到google上继续查找,最终找到了turn off hdmi的相关命令

// 关闭显示输出
sudo vcgencmd display_power 0
// 开启显示输出
sudo vcgencmd display_power 1

运行关闭显示输出命令后,屏幕熄灭、进入待机状态,此时树莓派依然是正常工作状态,如果你通过vnc连接树莓派,会发现一切如常,说明这个命令是在硬件层面关闭了显示输出

二、定时任务

编辑定时任务

sudo crontab -e

添加

# 每天7:30开启显示
30 7 * * * vcgencmd display_power 1

# 周一至周五每天8点关闭显示
0 8 * * 1-5 vcgencmd display_power 0

# 周一至周五每天19点开启显示
0 19 * * 1-5 vcgencmd display_power 1

# 每天0:30点关闭显示
30 0 * * * vcgencmd display_power 0

1.场景

试想,现在有这样一个场景,用户有一个观看视频的任务,任务的总时间为3min,观看达到这个时间用户即可完成此任务,我们需要做一个完成倒计时,来提示用户还剩多长时间即可完成该任务。

最容易想到的实现方式是使用定时器setInterval,每隔1s倒计时减1,用户暂停播放时清除定时器,开始播放时重启定时器

let countdown = 3 * 60
let timer = null

// 停止倒计时
const stop = () => {
clearInterval(timer)
timer = null
}

// 开始倒计时
const start = () => {
if (!timer) {
timer = setInterval(() => {
if (countdown > 0) {
countdown--
console.log('countdown: ', countdown)
} else {
console.log('done')
stop()
}
}, 1000)
}
}

这样的实现方式在一般情况下不会出现大的问题,但是如果用户暂停次数比较多的话,会发现用户实际播放时长大于倒计时所记的时长。

想个比较及端的例子,用户每次播放时长不超过1s,那么我们的倒计时一直保持不变,显然我们实现的倒计时是不准确的,不可靠的。

可能你会想着缩短setInterval的间隔来减少这种误差,但是间隔过小,又有阻塞的可能,定时器本身也不是那么精确,总有几毫秒的误差,而且显然这种做法略显笨拙,

这时我们就要思考是不是需要换一种实现方式,更优雅一些。

要想每次暂停播放不损失那不到1s的时间,我们只有精确到ms才行,既然不能用setInterval间隔1ms,那我们是可以取当前时间的毫秒数的,这个是非常精确的,本身就是以ms为单位的

let countdown = 3 * 60
let time = 0
let startTime = 0
let timer = null

// 停止倒计时
const stop = () => {
time += (Date.now() - startTime)
clearInterval(timer)
timer = null
}

// 开始倒计时
const start = () => {
if (!timer) {
startTime = Date.now()
timer = setInterval(() => {
let currentCountdown = countdown - Math.floor((Date.now() - startTime + time) / 1000)
if (currentCountdown > 0) {
console.log('countdown: ', currentCountdown)
} else {
console.log('done')
stop()
}
}, 1000)
}
}

这里我们依然使用了setInterval,但是只是用来每隔一秒读取倒计时,用来实现用户视图层的更新,实际与倒计时实现并无太大关联,我们可以封装一个倒计时

class Countdown {
// 传入倒计时初始值(ms)
constructor(countdown) {
this.lastTime = 0
this.startTime = null
this.countdown = countdown
this.stoped = true
}

// 开始计时
start() {
this.startTime = Date.now()
this.stoped = false
}

// 停止计时
stop() {
this.lastTime += Date.now() - this.startTime
this.stoped = true
}

// 获取倒计时
getCountdown() {
let result
if (this.stoped) {
result = this.countdown - this.lastTime
} else {
result = this.countdown - (Date.now() - this.startTime) - this.lastTime
}
return result >= 0 ? result : 0
}
}

一但我们创建了一个Countdown实例,就可以随时通过调用实例的getCountdown方法获取当前的倒计时。在未开始start时,会返回倒计时总秒数;

开始倒计时后,我们可以指定一定的时间间隔读取当前的倒计时,如果你想每秒更新view的倒计时,可以每秒取样,或是更小的时间间隔;

停止倒计时后,我们只能读取到一个固定值,除非再次开启倒计时;

倒计时为0时,将一直返回0,表示倒计时完成,可以将此视为完成标志;

以上我们已经可以拿到准确的倒计时,不论是倒计时还未完成,还是已经完成返回0,都能准确表示当前倒计时状态,但还是存在一个问题,就是我们无法知道倒计时是何时完成的,即便我们读取倒计时的值为0时,只能说明倒计时已经完成,却无法准确知道合适完成

所以这里还需要补充一个完成回调,来告诉我们倒计时何时完成,以便执行后续的业务逻辑

class Countdown {
// 传入倒计时初始值(ms)及完成回调
constructor(countdown, callback) {
this.lastTime = 0
this.startTime = null
this.countdown = countdown
this.callback = callback
this.timer = null
}

// 开始计时
start() {
this.startTime = Date.now()
this.timer = setTimeout(() => {
this.callback && this.callback()
}, this.countdown - this.lastTime)
}

// 停止计时
stop() {
this.lastTime += Date.now() - this.startTime
if (this.timer) {
clearTimeout(this.timer)
this.timer = null
}
}

// 获取倒计时
getCountdown() {
let result
if (this.timer) {
const currentTime = Date.now() - this.startTime + this.lastTime
result = this.countdown - currentTime
} else {
result = this.countdown - this.lastTime
}
return result >= 0 ? result : 0
}
}

1.常规用法

单选按钮通常用于用户在一组选项中选中某一项的场景中,一但用户选中其中一项,是不可以再次点击以取消选中的。

常见的写法有以下几种

无label的

<input type="radio" name="option" value="a" checked>选项A
<input type="radio" name="option" value="b">选项B

通过for关联

<input type="radio" id="optionA" name="option2" value="a" checked><label for="optionA">选项A</label>
<input type="radio" id="optionB" name="option2" value="b"><label for="optionB">选项B</label>

或是直接将input放到对应的label中

<label><input type="radio" name="option3" value="a" checked>选项A</label>
<label><input type="radio" name="option3" value="b">选项B</label>

2.取消选中

如果有需求需要允许用户将已经选中的单选按钮取消选中,这个需求其实并不是非常常见,但也算有实际的需求场景,还算合理
但是默认提供的单选按钮是无法取消选中的,那么只有通过相应的事件进行处理了

对于一组单选按钮,他们是相互关联的,选中其一,则其他的会取消选中;那么我们可以监听单选的click事件
如果
1.已经选中,那么设置元素的checked = false,取消选中
2.未选中,那么设置元素的checked = true, 选中;同组的其它单选元素的checked = false

但在实际处理中会发现,我们并不知道click前的状态,因为点击后,我们事件的e.target.checked都会为true,所以我们应该记录下click之前的状态

const radioEls = document.querySelectorAll('input[type=radio]')
radioEls.forEach(el => {
el._checked = el.checked
el.addEventListener('click', e => {
const target = e.target
if (target._checked) {
target.checked = target._checked = false
} else {
document.querySelectorAll(`input[name=${target.name}]`).forEach(el => {
el.checked = el._checked = false
})
target.checked = target._checked = true
}
})
})

查看效果