这一个月一直在研究 kindle,只是因为某一天逛网站的时候看到某个帖子,最新版本的固件可以解锁,然后就解锁了,发现 kindle 这个系统还是很有意思的。

kindle 里面有一组 lipc 命令行工具,包括

  • lipc-probe
  • lipc-set-prop
  • lipc-get-prop
  • lipc-send-event
  • lipc-wait-event

lipc-set-prop / lipc-get-prop 用来设定、读取系统参数,底层是使用 SQLite 存储。lipc-send-even / lipc-wait-event 接收发送系统时间。使用下面命令就可以监听系统的所有电源事件了:

lipc-wait-event -m com.lab126.powerd '*'

我手上这个 kindle 有两个 rtc,分别是 rtc0 和 rtc1。利用 RTC 在用户空间文件系统的接口,直接写入时间,然后进入休眠是可以正常唤醒的。

# 清空定时器
echo 0 > /sys/class/rtc/rtc0/wakealarm
# 设定 60s 后唤醒
echo +60 > /sys/class/rtc/rtc0/wakealarm
# 进入休眠状态
echo "mem" > /sys/power/state

如果想要接管 Kindle 系统的电源管理,这似乎已经够了。但是 kindle 系统有自带的电源管理系统,还有壁纸服务。使用上面的命令,过于暴力不够优雅。

在设备正常运行中,按下电源键,系统不会马上进入休眠状态,而是进入屏保状态,过一段时间之后才会休眠。具体的流程如下:

电源键按下,设备进入屏保界面,首先发送一次 goingToScreenSaver 事件,此时设备正常运行,包括 Wi-Fi 连接都没有断开。这个状态下等待一分钟,开始发送 readyToSuspend 事件,每次间隔 5s,总共发送 7 次,发送完这个事件之后就会进入休眠,这个阶段设备开始断开网络连接。休眠之前还会发送一次suspending 事件,这个事件之后 5s 左右就会进入设备休眠。上面的 readyToSuspend 阶段其实是可以延迟的休眠时间的,保证设备在下载电子书或是系统升级包的时候不会因为设备休眠而打断。

唤醒阶段,也是由电源键产生中断信号触发,系统恢复正常运行。由睡眠阶段后产生的第一个事件为wakeupFromSuspend 。之后依次发送 resuming > outOfScreenSaver > exitingScreenSaver 事件。

时序图如下:

   ╭────────╮
   │ Start  │ 
   ╰────────╯
       │   
       │
       │     ╭────────────────╮
       │ <-  │ Hit power key  │
       │     ╰────────────────╯
       │   
       │
╭────────────────────────╮  
│  goingToScreenSaver    │
╰────────────────────────╯
       │
  ╭─---------------─╮    
  │ Wait 1 minute   │
  ╰─---------------─╯ 
       │
       │
╭────────────────────╮
│ readyToSuspend x7  │
╰────────────────────╯
       │   
       │  
  ╭─────────────╮
  │ suspending  │
  ╰─────────────╯
       │
       │
       │     ╭────────────────╮
       │ <-  │ Hit power key  │
       │     ╰────────────────╯
       │   
       │
╭────────────────────╮
│ wakeupFromSuspend  │
╰────────────────────╯
       │   
       │
╭──────────────────╮
│  resuming        │
╰──────────────────╯
       │  
╭──────────────────╮
│ outOfScreenSaver │
╰──────────────────╯
       │  
╭────────────────────╮
│ exitingScreenSaver │
╰────────────────────╯
       │  
       │
   ╭────────╮
   │ End    │
   ╰────────╯

但是还有另外一种情况,就是设备没有进入休眠,用户再次按下电源键,这时候的事件触发顺序跳过中间的readyToSuspend/suspending / resuming 阶段。直接到了 t1TimerReset > outOfScreenSaver > exitingScreenSaver

   ╭────────╮
   │ Start  │ 
   ╰────────╯
       │   
       │
       │     ╭────────────────╮
       │ <-  │ Hit power key  │
       │     ╰────────────────╯
       │   
       │
╭────────────────────────╮  
│  goingToScreenSaver    │
╰────────────────────────╯
       │
       │     ╭────────────────╮
       │ <-  │ Hit power key  │
       │     ╰────────────────╯
       │  
╭────────────────╮
│ t1TimerReset   │
╰────────────────╯
       │  
       │  
╭──────────────────╮
│ outOfScreenSaver │
╰──────────────────╯
       │  
       │  
╭────────────────────╮
│ exitingScreenSaver │
╰────────────────────╯
       │  
       │
   ╭────────╮
   │ End    │
   ╰────────╯

前面说过,可以直接操作 RTC 在用户文件系统暴露的接口进行设备休眠,如果这么操作的话,上面的事件是不会触发的,并且有可能打断后台任务的执行。所以,理想的操作是使用系统提供的电源管理服务。

在进入屏保之后的 readyToSuspend 阶段可以使用 lipc-set-prop 命令设置一个唤醒时间。我之前曾试过在这个阶段,手动写入时间到 /sys/class/rtc/rtc0/wakealarm ,然后等待系统自动休眠,但是没有唤醒,猜测在最后阶段电源管理会使用 rtcWakeup 这个参数覆盖上面我设定的唤醒时间,大多数时候这个参数数值是 0,就是不会唤醒的意思。

这个参数只能在 readyToSuspend 阶段设置,提前或者之后设定都是不成功的。设置唤醒时间为一分钟之后的命令如下:

lipc-set-prop -i com.lab126.powerd rtcWakeup 60

如果是定时器触发的唤醒,并不会发送屏保的事件。只会有wakeupFromSuspend 事件,然后如果什么都不操作的话,一分钟后再次进入休眠的循环中。

   ╭────────╮
   │ Start  │ 
   ╰────────╯
       │     ╭────────────────╮
       │ <-  │ Hit power key  │
       │     ╰────────────────╯
╭────────────────────────╮  
│  goingToScreenSaver    │
╰────────────────────────╯
  ╭─---------------─╮    
  │ Wait 1 minute   │
  ╰─---------------─╯ 
╭────────────────────╮
│ readyToSuspend x7  │
╰────────────────────╯
  ╭─────────────╮
  │ suspending  │
  ╰─────────────╯
  ╭─---------------─╮
  │   Sleeping      │<──────────╮           
  ╰─---------------─╯           │           
       │                        │           
       │                        │           
╭────────────────────╮          │           
│ wakeupFromSuspend  │          │           
╰────────────────────╯          │           
       │                        │           
       │                        │           
  ╭─---------------─╮           │           
  │ Wait 1 minute   │           │           
  ╰─---------------─╯           │           
       │                        │            
       │                        │   
╭────────────────────╮          │   
│ readyToSuspend x7  │          │ 
╰────────────────────╯          │
       │                        │
  ╭─────────────╮               │
  │ suspending  │  ─────────────╯ 
  ╰─────────────╯
       │     ╭────────────────╮
       │ <-  │ Hit power key  │
       │     ╰────────────────╯
╭────────────────────╮
│ wakeupFromSuspend  │
╰────────────────────╯
╭──────────────────╮
│  resuming        │
╰──────────────────╯
╭──────────────────╮
│ outOfScreenSaver │
╰──────────────────╯
╭────────────────────╮
│ exitingScreenSaver │
╰────────────────────╯
   ╭────────╮
   │ End    │
   ╰────────╯

有了上面的时许图之后,只要在特定的事件做处理,就可以实现替换锁屏后的屏保,并且定时更换屏保。我是在wakeupFromSuspend 这个事件触发的时候进行屏保更换的操作,但是按下电源键进行系统的时候也会触发这个事件,这时候你再更新屏保显然不太合适,一个简单的方法就是这个事件触发的时候,在等一会看看有没有屏保事件发生,如果有,这就是用户执行的操作。

定时器唤醒之后的设备,用户是不可察觉的,屏幕不会亮起,屏保也不会切出去(屏幕能不能触摸我没有测试)。所以我用定时器,实现屏保更换,实际感觉切换的过程非常顺畅,不会出现屏幕多次刷新的情况。

我遇到一个非常的大的问题,就是唤醒后的网络是断开的,这个困扰了我非常久。直到我爬贴子看到有网友说,在睡眠之前断开Wi-Fi,唤醒之后连接Wi-Fi,这样可以保证网络正常。

我试过使用 wpa_cli 进行控制,但是没有成功。命令如下:

wpa_cli -i wlan0 reconnect

也许多次断开连接之后,应该可以吧。我也试过,唤醒之后断开Wi-Fi然后在连接也失败了。

解决方法就是在readyToSuspend 阶段执行命令

lipc-set-prop com.lab126.cmd wirelessEnable 0

然后在唤醒阶段执行开启命令:

lipc-set-prop com.lab126.cmd wirelessEnable 1

上面的命令是关闭和打开飞行模式,就是关闭Wi-Fi 和 打开Wi-Fi。如果使用电源键正常的关闭和唤醒,并不需要切休眠模式,就能快速连接到Wi-Fi,一定是有其他接口可以掉用吧。但是用户空间的这些程序 Amazon 并没有开源,网上也没啥研究资料,我也不会逆向,所以就用上面的方式解决这个问题。

还有另外一种方式解决上面休眠唤醒后网络连接的问题,就是模拟电源键按下,系统切出屏保,如果这时候没有进入离线模式,网络就会恢复连接吧。执行下面命令可以模拟电源键按下:

lipc-set-prop com.lab126.powerd powerButton 1

这个我就没有测试了,本来作为最后的手段使用的。因为按下电源键,屏幕会多次刷新,下载完数据之后,还需要再次模拟电源键进入休眠,这样维护的状态会变得很多,又不好看,还容易产生 bug,还不如直接读写 RTC 来的方便。

一些小技巧,屏幕保护是可以禁用的,屏保的原理是检查到goingToScreenSaver事件的时候,缓存当前屏幕内容,然后切换到屏保图片,当退出屏幕保护事件触发的时候,切回原来内容。启用屏保、禁用屏保在设备重启之后,会失效。如果没有屏保,按下电源键之后,屏幕内容不会变化,只是阅读灯关闭,屏幕触摸失效。

# 禁用屏保
lipc-set-prop -s com.lab126.blanket unload screensaver
# 启用屏保
lipc-set-prop -s com.lab126.blanket load screensaver

我编写的代码,定时下载指定 URL 的图片然后刷新到屏幕:kindle-calendar.zip
参考资料:https://www.mobileread.com/forums/showpost.php?p=3933978&postcount=27