├── .gitignore ├── error.png ├── screenshot.jpg ├── bin └── rsvg-convert ├── lib ├── librsvg-2.so.2 └── libcroco-0.6.so.3 ├── sync2kindle.sh ├── menu.json ├── weatherstation.conf ├── config.py ├── config.xml ├── create-png.sh ├── wifi.sh ├── README.md ├── weather2svg.py ├── kindle-weather.sh └── weather-preprocess.svg /.gitignore: -------------------------------------------------------------------------------- 1 | config.py 2 | weather-script-output.svg 3 | out.png 4 | __pycache__ 5 | -------------------------------------------------------------------------------- /error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattzzw/kindle-weatherstation/HEAD/error.png -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattzzw/kindle-weatherstation/HEAD/screenshot.jpg -------------------------------------------------------------------------------- /bin/rsvg-convert: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattzzw/kindle-weatherstation/HEAD/bin/rsvg-convert -------------------------------------------------------------------------------- /lib/librsvg-2.so.2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattzzw/kindle-weatherstation/HEAD/lib/librsvg-2.so.2 -------------------------------------------------------------------------------- /sync2kindle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | rsync -zrvh --update * root@kindle:/mnt/us/extensions/weatherstation 3 | -------------------------------------------------------------------------------- /lib/libcroco-0.6.so.3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattzzw/kindle-weatherstation/HEAD/lib/libcroco-0.6.so.3 -------------------------------------------------------------------------------- /menu.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "name": "Weatherstation", 5 | "action": "setsid ./kindle-weather.sh &" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /weatherstation.conf: -------------------------------------------------------------------------------- 1 | start on started cmd 2 | stop on stopping cmd 3 | 4 | export LANG LC_ALL 5 | 6 | chdir /mnt/us/extensions/weatherstation 7 | exec ./kindle-weather.sh 8 | 9 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | ### API URL, don't touch 2 | api_url = 'https://api.openweathermap.org/data/3.0/onecall?' 3 | 4 | ### Change your key 5 | api_key = '00000000000000000000000000000000' 6 | 7 | ### Adjust your lat/lon, units and language 8 | ### --> https://openweathermap.org/api/one-call-api 9 | api_param = 'lat=53.5&lon=10&units=metric&lang=de' 10 | -------------------------------------------------------------------------------- /config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Weatherstation 5 | 1.0 6 | mattzz 7 | KAUL-SELF-MENU-ADD-ONS 8 | 9 | 10 | menu.json 11 | 12 | 13 | -------------------------------------------------------------------------------- /create-png.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PWD=$(pwd) 4 | export LD_LIBRARY_PATH=$PWD/lib 5 | 6 | $PWD/weather2svg.py 7 | if [ $? -eq 0 ] 8 | then 9 | $PWD/bin/rsvg-convert --background-color=white -o kindle-weather.png weather-script-output.svg 10 | # $PWD/bin/pngcrush -q -c 0 out.png kindle-weather.png 11 | else 12 | cp error.png kindle-weather.png 13 | fi 14 | -------------------------------------------------------------------------------- /wifi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | id="`wpa_cli add_network | sed -n '2p'`" 3 | echo $id 4 | 5 | exec="`wpa_cli << EOF 6 | set_network $id ssid \"Your SSID\" 7 | set_network $id psk \"Your PW\" 8 | set_network $id key_mgmt WPA-PSK 9 | set_network $id group CCMP TKIP 10 | set_network $id proto RSN WPA 11 | set_network $id pairwise CCMP TKIP 12 | select_network $id 13 | enable_network $id 14 | quit 15 | EOF 16 | `" 17 | echo $exec 18 | udhcpc -i wlan0 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kindle Weatherstation 2 | 3 | This is a serverless implementation of a weatherstation for the Kindle (PW2) 4 | that is also optimized for battery runtime (~ a month). 5 | 6 | The Kindle fetches weather data from Openweathermap.org every 60 minutes, creates a SVG file 7 | based on a template, converts the SVG to PNG, displays the newly generated PNG and 8 | goes to sleep (STR/suspend to RAM) for the remaining time. 9 | 10 | ![screenshot](./screenshot.jpg) 11 | 12 | ## What's what 13 | * `weather2svg.py`: Queries weather data and assembles a SVG file based on template 14 | * `create-png.sh`: Gets data, converts svg file to png and compresses png file 15 | * `kindle-weather.sh`: Main loop, gets and displays data, suspend to RAM and wakeup 16 | * `weather-preprocess.svg`: SVG template 17 | * `config.xml`: KUAL config file 18 | * `menu.json`: KUAL config file 19 | * `sync2kindle.sh`: rsyncs all files to Kindle, helps during development/debugging 20 | * `wifi.sh`: Wifi helper script 21 | * `weatherstation.conf`: Upstart script 22 | 23 | [rsvg-convert](https://github.com/ImageMagick/librsvg) and [pngcrush](https://pmt.sourceforge.io/pngcrush/) binaries and libs are included. 24 | 25 | ## Kindle preparation: 26 | * Hack the kindle (doh!) --> [Mobileread Forum Thread](https://www.mobileread.com/forums/showthread.php?t=320564) 27 | * Install KUAL 28 | * Add USBnetworking and python (via MPRI) 29 | 30 | ## Installation: 31 | * Adjust key and location in `config.py` 32 | * Adjust SSID and passphrase in `wifi.sh` 33 | * Create directory /mnt/us/extensions/weatherstation 34 | * Copy everything to the newly created directory (or use sync2kindle.sh) 35 | 36 | ## Upstart script 37 | Optionally, you can use a startup script to start the weatherstation automatically when the Kindle comes up. 38 | 39 | * `$ mntroot rw` 40 | * `$ cp /mnt/us/extensions/weatherstation/weatherstation.conf /etc/upstart` 41 | * `$ mntroot ro` 42 | * `$ start weatherstation` 43 | 44 | ## Stopping weatherstation: 45 | * Either press button, quickly login and do a `killall kindle-weather.sh` (or if you are using the upstart script `stop weatherstation`). 46 | * Or force reboot kindle by holding powerbutton ~10 seconds 47 | 48 | ## Credits: 49 | * Building on ideas and code from 50 | * https://mpetroff.net/2012/09/kindle-weather-display/ and 51 | * https://github.com/nicoh88/kindle-kt3_weatherdisplay_battery-optimized 52 | -------------------------------------------------------------------------------- /weather2svg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import requests 4 | from datetime import datetime 5 | import json 6 | import codecs 7 | import subprocess 8 | import config 9 | 10 | 11 | api_url = config.api_url + config.api_param + '&appid=' + config.api_key 12 | 13 | try: 14 | r = requests.get(api_url) 15 | r.raise_for_status() 16 | except requests.exceptions.HTTPError as errh: 17 | print ("Http Error: ",errh) 18 | exit(-1) 19 | except requests.exceptions.RequestException as e: 20 | print ("Problem getting data: " + str(e)) 21 | exit(-1) 22 | 23 | # read the data from the URL and print it 24 | weather = r.json() 25 | 26 | # process SVG 27 | output = codecs.open('weather-preprocess.svg', 'r', encoding='utf-8').read() 28 | 29 | output = output.replace('#NOW', datetime.fromtimestamp(weather['current']['dt']).strftime('%d %b %Y, %H:%M:%S')) 30 | 31 | # current weather 32 | output = output.replace('#IC00',weather['current']['weather'][0]['icon']) 33 | output = output.replace('#TN','{:.0f}'.format(weather['current']['temp'])) 34 | output = output.replace('#HI00','{:.0f}'.format(weather['daily'][0]['temp']['max'])) 35 | output = output.replace('#LO00','{:.0f}'.format(weather['daily'][0]['temp']['min'])) 36 | output = output.replace('#SUMNOW', weather['current']['weather'][0]['description']) 37 | # output = output.replace('#SUMHR', weather['hourly'][0]['weather'][0]['description']) 38 | output = output.replace('#DP0', '{:.0f}'.format(weather['daily'][0]['pop'] * 100)) 39 | # rain and snow keys are not always present. 40 | precip = 0 41 | if 'rain' in weather['daily'][0]: 42 | precip = weather['daily'][0]['rain'] 43 | if 'snow' in weather['daily'][0]: 44 | precip += weather['daily'][0]['snow'] 45 | output = output.replace('#DM0', '{:.2f}'.format(precip)) 46 | output = output.replace('#DBP', '{:.0f}'.format(weather['daily'][0]['pressure'])) 47 | output = output.replace('#DHU', '{:.0f}'.format(weather['daily'][0]['humidity'])) 48 | 49 | output = output.replace('#SR', datetime.fromtimestamp(weather['daily'][0]['sunrise']).strftime('%H:%M')) 50 | output = output.replace('#SS', datetime.fromtimestamp(weather['daily'][0]['sunset']).strftime('%H:%M')) 51 | 52 | # battery 53 | # depending on board type 54 | # could also be e.g. /sys/devices/system/yoshi_battery/yoshi_battery0/battery_capacity 55 | proc_out = subprocess.Popen("gasgauge-info -s".split(), 56 | stdout=subprocess.PIPE, 57 | stderr=subprocess.STDOUT) 58 | battery_capacity,stderr = proc_out.communicate() 59 | output = output.replace('#BAT', battery_capacity.decode("utf-8")) 60 | 61 | # next 12 hours 62 | for i in range(1, 13): 63 | istr = "{:02d}".format(i) 64 | output = output.replace('#IC'+istr, weather['hourly'][i]['weather'][0]['icon']) 65 | output = output.replace('#TM'+istr, str(datetime.fromtimestamp(weather['hourly'][i]['dt']).strftime('%H:%M'))) 66 | output = output.replace('#TE'+istr, '{:.0f}'.format(weather['hourly'][i]['temp'])) 67 | output = output.replace('#PP'+istr, '{:.0f}'.format(weather['hourly'][i]['pop'] * 100)) 68 | precip = 0 69 | if 'rain' in weather['hourly'][i]: 70 | precip = weather['hourly'][i]['rain']['1h'] 71 | if 'snow' in weather['hourly'][i]: 72 | precip += weather['hourly'][i]['snow']['1h'] 73 | output = output.replace('#PA'+istr, '{:.2f}'.format(precip)) 74 | 75 | #next 7 days 76 | for i in range (1, 8): 77 | istr = str(i) 78 | output = output.replace('#DA'+istr, str(datetime.fromtimestamp(weather['daily'][i]['dt']).strftime('%a %d.%-m.'))) 79 | output = output.replace('#DI'+istr, weather['daily'][i]['weather'][0]['icon']) 80 | output = output.replace('#DH'+istr, '{:.0f}'.format(weather['daily'][i]['temp']['max'])) 81 | output = output.replace('#DL'+istr, '{:.0f}'.format(weather['daily'][i]['temp']['min'])) 82 | output = output.replace('#DP'+istr, '{:.0f}'.format(weather['daily'][i]['pop'] * 100)) 83 | precip = 0 84 | if 'rain' in weather['daily'][i]: 85 | precip = weather['daily'][i]['rain'] 86 | if 'snow' in weather['daily'][i]: 87 | precip += weather['daily'][i]['snow'] 88 | output = output.replace('#DM'+istr, '{:.2f}'.format(precip)) 89 | 90 | #output = output.replace('#SUMDAILY', weather['daily']['summary']) 91 | 92 | codecs.open('weather-script-output.svg', 'w', encoding='utf-8').write(output) 93 | -------------------------------------------------------------------------------- /kindle-weather.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PWD=$(pwd) 4 | LOG="/mnt/us/weatherstation.log" 5 | SLEEP_MINUTES=60 6 | FBINK="fbink -q" 7 | FONT="regular=/usr/java/lib/fonts/Palatino-Regular.ttf" 8 | 9 | ### uncomment/adjust according to your hardware 10 | #PW3 11 | #FBROTATE="/sys/devices/platform/imx_epdc_fb/graphics/fb0/rotate" 12 | #BACKLIGHT="/sys/devices/platform/imx-i2c.0/i2c-0/0-003c/max77696-bl.0/backlight/max77696-bl/brightness" 13 | 14 | #PW2 15 | FBROTATE="/sys/devices/platform/mxc_epdc_fb/graphics/fb0/rotate" 16 | BACKLIGHT="/sys/devices/system/fl_tps6116x/fl_tps6116x0/fl_intensity" 17 | 18 | wait_wlan_connected() { 19 | return `lipc-get-prop com.lab126.wifid cmState | grep CONNECTED | wc -l` 20 | } 21 | 22 | wait_wlan_ready() { 23 | return `lipc-get-prop com.lab126.wifid cmState | grep -e READY -e PENDING -e CONNECTED | wc -l` 24 | } 25 | 26 | ### Dim Backlight 27 | echo -n 0 > $BACKLIGHT 28 | 29 | ### Prepare Kindle, shutdown framework etc. 30 | echo "------------------------------------------------------------------------" >> $LOG 31 | echo "`date '+%Y-%m-%d_%H:%M:%S'`: Starting up, killing framework et. al." >> $LOG 32 | 33 | ### stop processes that we don't need 34 | stop lab126_gui 35 | ### give an update to the outside world... 36 | echo 0 > $FBROTATE 37 | $FBINK -w -c -f -m -t $FONT,size=20,top=410,bottom=0,left=0,right=0 "Starting weatherstation..." > /dev/null 2>&1 38 | #echo 3 > $FBROTATE 39 | sleep 1 40 | ### keep stopping stuff 41 | stop otaupd 42 | stop phd 43 | stop tmd 44 | stop x 45 | stop todo 46 | stop mcsd 47 | stop archive 48 | stop dynconfig 49 | stop dpmd 50 | stop appmgrd 51 | stop stackdumpd 52 | sleep 2 53 | 54 | # At this point we should be left with a more or less Amazon-free environment 55 | # I leave 56 | # - powerd & deviced 57 | # - lipc-daemon 58 | # - rcm 59 | # running. 60 | 61 | ### If we have a wan module installed... 62 | #if [ -f /usr/sbin/wancontrol ] 63 | #then 64 | # wancontrol wanoffkill 65 | #fi 66 | 67 | ### Disable Screensaver 68 | lipc-set-prop com.lab126.powerd preventScreenSaver 1 69 | 70 | echo "`date '+%Y-%m-%d_%H:%M:%S'`: Entering main loop..." >> $LOG 71 | 72 | while true; do 73 | 74 | NOW=$(date +%s) 75 | 76 | let SLEEP_SECONDS=60*SLEEP_MINUTES 77 | let WAKEUP_TIME=$NOW+SLEEP_SECONDS 78 | echo `date '+%Y-%m-%d_%H:%M:%S'`: Wake-up time set for `date -d @${WAKEUP_TIME}` >> $LOG 79 | 80 | ### Dim Backlight 81 | echo -n 0 > $BACKLIGHT 82 | ### Force landscape mode 83 | echo 0 > $FBROTATE 84 | 85 | ### Disable CPU Powersave 86 | echo ondemand > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 87 | 88 | lipc-set-prop com.lab126.cmd wirelessEnable 1 89 | ### Wait for wifi interface to come up 90 | echo `date '+%Y-%m-%d_%H:%M:%S'`: Waiting for wifi interface to come up... >> $LOG 91 | while wait_wlan_ready; do 92 | sleep 1 93 | done 94 | 95 | ### Wifi interface is up, connect to access point. 96 | ./wifi.sh 97 | 98 | ### Wait for WIFI connection 99 | TRYCNT=0 100 | NOWIFI=0 101 | echo `date '+%Y-%m-%d_%H:%M:%S'`: Waiting for wifi interface to become ready... >> $LOG 102 | while wait_wlan_connected; do 103 | if [ ${TRYCNT} -gt 30 ]; then 104 | ### waited long enough 105 | echo "`date '+%Y-%m-%d_%H:%M:%S'`: No Wifi... ($TRYCNT)" >> $LOG 106 | NOWIFI=1 107 | eips -f -g $PWD/error.png 108 | break 109 | fi 110 | sleep 1 111 | let TRYCNT=$TRYCNT+1 112 | done 113 | 114 | echo `date '+%Y-%m-%d_%H:%M:%S'`: WIFI connected! >> $LOG 115 | 116 | ### Get Weatherdata 117 | echo `date '+%Y-%m-%d_%H:%M:%S'`: Getting weatherdata >> $LOG 118 | rm -f $PWD/kindle-weather.png 119 | 120 | if $PWD/create-png.sh; then 121 | #eips -f -g $PWD/kindle-weather.png 122 | $FBINK -c -f -i kindle-weather.png -g w=-1,h=-1 123 | else 124 | echo `date '+%Y-%m-%d_%H:%M:%S'`: Something went wrong getting weatherdata >> $LOG 125 | eips -f -g $PWD/error.png 126 | sleep 60 127 | fi 128 | 129 | #echo `date -d @${WAKEUP_TIME}` | xargs -0 eips 130 | BAT=$(gasgauge-info -s | sed s/%//) 131 | # echo $BAT | xargs -0 eips -f 1 1 132 | echo `date '+%Y-%m-%d_%H:%M:%S'`: Battery level: $BAT >> $LOG 133 | 134 | ### Enable powersave 135 | echo powersave > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 136 | 137 | ### flight mode on... 138 | lipc-set-prop com.lab126.cmd wirelessEnable 0 139 | 140 | ### 3 secs necessary on Kindle PW (EY21) to avoid random hangs 141 | sleep 3 142 | 143 | ### set wake up time to one hour 144 | rtcwake -d /dev/rtc1 -m no -s $SLEEP_SECONDS 145 | ### Go into Suspend to Memory (STR) 146 | echo `date '+%Y-%m-%d_%H:%M:%S'`: Sleeping now... >> $LOG 147 | echo "mem" > /sys/power/state 148 | done 149 | -------------------------------------------------------------------------------- /weather-preprocess.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | image/svg+xml 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | #NOW 73 | Max: 74 | #HI00 75 | °C 76 | Min: 77 | #LO00 78 | °C 79 | #TN 80 | °C 81 | #SUMNOW 82 | 83 | #TM01 84 | 85 | 86 | #TE01°C 87 | #PP01% 88 | #PA01mm 89 | 90 | 91 | 92 | 93 | #TM02 94 | 95 | 96 | #TE02°C 97 | #PP02% 98 | #PA02mm 99 | 100 | 101 | 102 | 103 | #TM03 104 | 105 | 106 | #TE03°C 107 | #PP03% 108 | #PA03mm 109 | 110 | 111 | 112 | 113 | #TM04 114 | 115 | 116 | #TE04°C 117 | #PP04% 118 | #PA04mm 119 | 120 | 121 | 122 | 123 | #TM05 124 | 125 | 126 | #TE05°C 127 | #PP05% 128 | #PA05mm 129 | 130 | 131 | 132 | 133 | #TM06 134 | 135 | 136 | #TE06°C 137 | #PP06% 138 | #PA06mm 139 | 140 | 141 | 142 | 143 | #TM07 144 | 145 | 146 | #TE07°C 147 | #PP07% 148 | #PA07mm 149 | 150 | 151 | 152 | 153 | #TM08 154 | 155 | 156 | #TE08°C 157 | #PP08% 158 | #PA08mm 159 | 160 | 161 | 162 | 163 | #TM09 164 | 165 | 166 | #TE09°C 167 | #PP09% 168 | #PA09mm 169 | 170 | 171 | 172 | 173 | #TM10 174 | 175 | 176 | #TE10°C 177 | #PP10% 178 | #PA10mm 179 | 180 | 181 | 182 | 183 | #TM11 184 | 185 | 186 | #TE11°C 187 | #PP11% 188 | #PA11mm 189 | 190 | 191 | 192 | 193 | #TM12 194 | 195 | 196 | #TE12°C 197 | #PP12% 198 | #PA12mm 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | #SR 211 | #SS 212 | #DP0% 213 | #DM0mm 214 | #DBPmbar 215 | #DHU% 216 | #BAT% 217 | 218 | 219 | 220 | 221 | #DA1 222 | 223 | 224 | #DL1..#DH1°C 225 | #DP1% 226 | #DM1mm 227 | 228 | 229 | 230 | 231 | 232 | 233 | #DA2 234 | 235 | 236 | #DL2..#DH2°C 237 | #DP2% 238 | #DM2mm 239 | 240 | 241 | 242 | 243 | 244 | 245 | #DA3 246 | 247 | 248 | #DL3..#DH3°C 249 | #DP3% 250 | #DM3mm 251 | 252 | 253 | 254 | 255 | 256 | 257 | #DA4 258 | 259 | 260 | #DL4..#DH4°C 261 | #DP4% 262 | #DM4mm 263 | 264 | 265 | 266 | 267 | 268 | 269 | #DA5 270 | 271 | 272 | #DL5..#DH5°C 273 | #DP5% 274 | #DM5mm 275 | 276 | 277 | 278 | 279 | 280 | 281 | #DA6 282 | 283 | 284 | #DL6..#DH6°C 285 | #DP6% 286 | #DM6mm 287 | 288 | 289 | 290 | 291 | 292 | 293 | #DA7 294 | 295 | 296 | #DL7..#DH7°C 297 | #DP7% 298 | #DM7mm 299 | 300 | 301 | 302 | 303 | 304 | --------------------------------------------------------------------------------