#!/bin/sh ## ## cloudslate -- make this Kindle the slate board. ## ## Takes the screen from the Kindle UI, then polls for whatever has been ## published and paints it, giving the UI back when the hold expires. Launched ## from the library by sh_integration; settings come from cloudslate.config ## next to it, written by `cloudslateassist -a install`. ## ## XXX every byte of stdout gets painted to the screen by sh_integration, so ## everything here is redirected to the log. ## US=/mnt/us FBINK=$US/libkh/bin/fbink IMG=$US/cloudslate.png SUM=$US/cloudslate.sum BEAT=$US/cloudslate.beat LOG=$US/cloudslate.log URL= POLL_SECONDS=900 HOLD_SECONDS=0 FL_LEVEL=0 [ -f $US/cloudslate.config ] && . $US/cloudslate.config ## Seconds of this run spent suspended. ASLEEP=0 ## The panel is still moving after fbink returns; suspending now freezes it ## mid-waveform. SETTLE_SECONDS=3 FETCH_TRIES=3 FETCH_PAUSE=10 RTC=$(ls -d /sys/class/rtc/*/ 2>/dev/null | head -1) RTC=${RTC}wakealarm ## Suspending needs both a way down and a timer to come back up. can_suspend() { [ -f "$RTC" ] || return 1 grep -q mem /sys/power/state 2>/dev/null } ## Seconds from now. Kernels differ on whether wakealarm takes a relative or ## an absolute value. set_alarm() { echo 0 > $RTC 2>/dev/null echo "+$1" > $RTC 2>/dev/null a=$(cat $RTC 2>/dev/null) if [ -z "$a" ] || [ "$a" = "0" ] ; then echo $(( $(date +%s) + $1 )) > $RTC 2>/dev/null fi } clear_alarm() { echo 0 > $RTC 2>/dev/null ; } ## Suspend to RAM for this many seconds, or until the power button cuts it ## short. ## Returns 0 when the button woke us, 1 when the alarm did. doze() { before=$(date +%s) if can_suspend ; then set_alarm $1 echo mem > /sys/power/state 2>>$LOG slept=$(( $(date +%s) - before )) clear_alarm ASLEEP=$(( ASLEEP + slept )) ## A kernel that refuses returns at once. if [ $slept -lt 2 ] && [ $1 -gt 5 ] ; then echo "suspend returned in ${slept}s at $(date) -- staying awake" sleep $(( $1 - slept )) fi else sleep $1 fi [ $(( $(date +%s) - before )) -lt $(( $1 - 10 )) ] } ## The frontlight is the only real power draw here -- static e-ink costs ## nothing. Remember the setting, turn it down to FL_LEVEL, put it back on the ## way out. E-ink is reflective, so 0 needs a lit room. ## A wrong property name reads back empty and this all no-ops. FL_SAVED= ## flIntensity 0 still leaves the LEDs faintly lit. FL_NODE=$(ls /sys/class/backlight/*/brightness 2>/dev/null | head -1) light_off() { FL_SAVED=$(lipc-get-prop com.lab126.powerd flIntensity 2>/dev/null) echo "frontlight was '$FL_SAVED', node=${FL_NODE:-none}" >> $LOG [ -n "$FL_SAVED" ] && lipc-set-prop com.lab126.powerd flIntensity $FL_LEVEL 2>> $LOG if [ "$FL_LEVEL" = 0 ] && [ -n "$FL_NODE" ] ; then echo 0 > $FL_NODE 2>> $LOG echo "frontlight node now $(cat $FL_NODE 2>/dev/null)" >> $LOG fi } light_restore() { [ -n "$FL_SAVED" ] && lipc-set-prop com.lab126.powerd flIntensity "$FL_SAVED" 2>> $LOG } ## Always give the Kindle back, on every exit path. restore() { trap - EXIT INT TERM echo "=== restoring UI at $(date)" >> $LOG rm -f $BEAT clear_alarm light_restore start lab126_gui >> $LOG 2>&1 || start framework >> $LOG 2>&1 lipc-set-prop com.lab126.powerd preventScreenSaver 0 >> $LOG 2>&1 } trap restore EXIT INT TERM ## e-ink refreshes are visually expensive; only repaint when the slate changed changed() { new=$(md5sum "$IMG" 2>/dev/null | cut -d' ' -f1) [ -z "$new" ] && return 1 [ "$new" = "$(cat $SUM 2>/dev/null)" ] && return 1 echo "$new" > $SUM return 0 } ## Charge left, as a percentage. battery() { lipc-get-prop com.lab126.powerd battLevel 2>/dev/null } ## What the cell itself reports -- design capacity against what it still ## holds. Empty on a device that does not expose it. battery_detail() { cat /sys/class/power_supply/*/uevent 2>/dev/null } ## Microamps leaving the cell. battery_current() { cat /sys/class/power_supply/*/current_now 2>/dev/null | head -1 } ## Share of the run spent suspended, as a percentage. duty() { [ -z "$started" ] && return up=$(( $(date +%s) - started )) [ $up -gt 0 ] && echo $(( ASLEEP * 100 / up )) } ## The corner line: charge left, and the share of the run spent suspended. ## Either half is omitted when there is nothing to say. Empty if neither. status_line() { line= [ -n "$batt" ] && line="${batt}%" if [ $ASLEEP -gt 0 ] ; then [ -n "$line" ] && line="$line / " line="${line}$(duty)%" fi echo "$line" } ## Pull the published slate to $IMG.new. The radio is still associating for ## the first seconds after a resume, so an early try fails outright rather ## than timing out. fetch() { try=1 while : ; do if curl -sf -m 60 -o "$IMG.new" "$URL" ; then [ $try -gt 1 ] && echo "fetched on try $try at $(date)" return 0 fi rm -f "$IMG.new" [ $try -ge $FETCH_TRIES ] && return 1 sleep $FETCH_PAUSE try=$((try + 1)) done } ## Compose into the framebuffer, then drive every pixel once: a refresh moves ## only what changed, and ink that never changes fades where it sits. GC16 is ## the only waveform with sixteen levels. paint() { $FBINK -b -g file=$IMG > /dev/null 2>&1 batt=$(battery) corner=$(status_line) [ -n "$corner" ] && \ $FBINK -b -q -S 3 -C GRAY9 -x 2 -y -2 "$corner" > /dev/null $FBINK -f -W GC16 -s > /dev/null 2>&1 sleep $SETTLE_SECONDS } { echo "=== $(date) cloudslate starting, url=$URL" echo "suspend=$(can_suspend && echo yes || echo no) rtc=$RTC" [ -z "$URL" ] && { echo "no URL configured -- run cloudslateassist -a install"; exit 1; } stop lab126_gui 2>&1 || stop framework 2>&1 sleep 2 ## blanket -t screensaver survives the stop and will repaint if powerd ## ever fires the screensaver, so this is load-bearing lipc-set-prop com.lab126.powerd preventScreenSaver 1 2>&1 light_off battery_detail ## The screen isn't ours at startup -- the Kindle UI has been on it since we ## last ran -- so forget what we last painted or the checksum will skip the ## first paint and leave a blank screen. rm -f $SUM ## HOLD_SECONDS=0 runs until something stops us; anything else hands the ## Kindle back after that long. Recovery either way is a reboot. ## A suspended device does not advance sleep's idea of time. started=$(date +%s) forced=1 while : ; do if fetch ; then mv "$IMG.new" "$IMG" if changed || [ $forced -eq 1 ] ; then paint echo "painted at $(date) batt=${batt:-?}% cur=$(battery_current)" else echo "unchanged at $(date)" fi else echo "fetch failed at $(date)" rm -f "$IMG.new" fi forced=0 date +%s > $BEAT [ $HOLD_SECONDS -ne 0 ] && \ [ $(( $(date +%s) - started )) -ge $HOLD_SECONDS ] && break if doze $POLL_SECONDS ; then echo "woken early at $(date)" forced=1 fi date +%s > $BEAT done echo "=== hold expired at $(date)" } >> $LOG 2>&1